import type { DefinitionCollection, DefinitionSubscription, SiteDefinition } from "./types";

export interface DefinitionConfiguration {
  customDefinitions: SiteDefinition[];
  disabledDefinitionIds: string[];
  subscriptions: DefinitionSubscription[];
  subscriptionCollections: DefinitionCollection[];
}

export type DefinitionValidation =
  | { valid: true; value: DefinitionConfiguration }
  | { valid: false; errors: string[] };

const ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;

export function validateDefinitionConfiguration(value: unknown): DefinitionValidation {
  if (!isRecord(value)) return invalid("Definition configuration must be an object.");
  const customDefinitions = value.customDefinitions;
  const disabledDefinitionIds = value.disabledDefinitionIds;
  const subscriptions = value.subscriptions ?? [];
  const subscriptionCollections = value.subscriptionCollections ?? [];
  if (!Array.isArray(customDefinitions)) return invalid("customDefinitions must be an array.");
  if (!Array.isArray(disabledDefinitionIds) || disabledDefinitionIds.some((id) => typeof id !== "string" || !ID_PATTERN.test(id))) {
    return invalid("disabledDefinitionIds must be an array of valid definition IDs.");
  }
  if (!Array.isArray(subscriptions) || !Array.isArray(subscriptionCollections)) return invalid("Subscription state must contain arrays.");

  const errors: string[] = [];
  const definitions: SiteDefinition[] = [];
  const ids = new Set<string>();
  customDefinitions.forEach((definition, index) => {
    const validation = validateSiteDefinition(definition, `customDefinitions[${index}]`);
    if (!validation.valid) errors.push(...validation.errors);
    else if (ids.has(validation.value.id)) errors.push(`Duplicate definition ID: ${validation.value.id}.`);
    else { ids.add(validation.value.id); definitions.push(validation.value); }
  });
  const validSubscriptions = subscriptions.map((subscription, index) => validateSubscription(subscription, `subscriptions[${index}]`, errors)).filter((subscription): subscription is DefinitionSubscription => Boolean(subscription));
  const validCollections = subscriptionCollections.map((collection, index) => validateCollection(collection, `subscriptionCollections[${index}]`, errors)).filter((collection): collection is DefinitionCollection => Boolean(collection));
  const subscriptionIds = new Set<string>();
  for (const subscription of validSubscriptions) if (subscriptionIds.has(subscription.id)) errors.push(`Duplicate subscription ID: ${subscription.id}.`); else subscriptionIds.add(subscription.id);
  return errors.length > 0 ? { valid: false, errors } : { valid: true, value: { customDefinitions: definitions, disabledDefinitionIds: [...new Set(disabledDefinitionIds)], subscriptions: validSubscriptions, subscriptionCollections: validCollections } };
}

export function validateDefinitionCollection(value: unknown): { valid: true; value: DefinitionCollection } | { valid: false; errors: string[] } {
  const errors: string[] = [];
  const collection = validateCollection(value, "collection", errors);
  return collection && errors.length === 0 ? { valid: true, value: collection } : { valid: false, errors: errors.length ? errors : ["Invalid definition collection."] };
}

export function validateSiteDefinition(value: unknown, path = "definition"): { valid: true; value: SiteDefinition } | { valid: false; errors: string[] } {
  if (!isRecord(value)) return invalid(`${path} must be an object.`);
  const errors: string[] = [];
  const id = value.id;
  const name = value.name;
  if (typeof id !== "string" || !ID_PATTERN.test(id)) errors.push(`${path}.id must use lowercase letters, digits, and hyphens.`);
  if (typeof name !== "string" || !name.trim()) errors.push(`${path}.name is required.`);
  const urlPatterns = stringArray(value.urlPatterns, `${path}.urlPatterns`, errors, isUrlPattern);
  const requiredSelectors = stringArray(value.requiredSelectors, `${path}.requiredSelectors`, errors, isCssSelector);
  const post = value.post;
  if (!isRecord(post)) errors.push(`${path}.post must be an object.`);
  const rootSelectors = isRecord(post) ? stringArray(post.rootSelectors, `${path}.post.rootSelectors`, errors, isCssSelector) : [];
  const textSelectors = isRecord(post) ? stringArray(post.textSelectors, `${path}.post.textSelectors`, errors, isCssSelector) : [];
  const excludedSelectors = isRecord(post) && post.excludedSelectors !== undefined ? stringArray(post.excludedSelectors, `${path}.post.excludedSelectors`, errors, isCssSelector, false) : undefined;
  const idAttributes = isRecord(post) && post.idAttributes !== undefined ? stringArray(post.idAttributes, `${path}.post.idAttributes`, errors, (attribute) => /^[\w:-]+$/.test(attribute), false) : undefined;
  const permalinkSelectors = isRecord(post) && post.permalinkSelectors !== undefined ? stringArray(post.permalinkSelectors, `${path}.post.permalinkSelectors`, errors, isCssSelector, false) : undefined;
  if (errors.length > 0) return { valid: false, errors };
  return { valid: true, value: { id: id as string, name: (name as string).trim(), urlPatterns, requiredSelectors, post: { rootSelectors, textSelectors, ...(excludedSelectors?.length ? { excludedSelectors } : {}), ...(idAttributes?.length ? { idAttributes } : {}), ...(permalinkSelectors?.length ? { permalinkSelectors } : {}) } } };
}

export function isUrlPattern(pattern: string): boolean {
  return pattern === "<all_urls>" || /^(?:\*|https?|file):\/\/(?:\*|\*\.[^/*]+|[^/*]+)\/.*$/.test(pattern);
}

function stringArray(value: unknown, path: string, errors: string[], predicate: (item: string) => boolean, required = true): string[] {
  if (!Array.isArray(value) || (required && value.length === 0) || value.some((item) => typeof item !== "string" || !predicate(item))) {
    errors.push(`${path} must be ${required ? "a non-empty " : "an optional "}array of valid values.`);
    return [];
  }
  return value as string[];
}

function isCssSelector(selector: string): boolean {
  if (typeof document === "undefined") return selector.trim().length > 0;
  try { document.createElement("div").matches(selector); return true; } catch { return false; }
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function validateSubscription(value: unknown, path: string, errors: string[]): DefinitionSubscription | undefined {
  if (!isRecord(value) || typeof value.id !== "string" || !ID_PATTERN.test(value.id) || typeof value.name !== "string" || !value.name.trim() || typeof value.url !== "string" || !/^https?:\/\//.test(value.url) || typeof value.enabled !== "boolean" || typeof value.priority !== "number" || !Number.isFinite(value.priority)) {
    errors.push(`${path} is invalid.`); return undefined;
  }
  return { id: value.id, name: value.name.trim(), url: value.url, enabled: value.enabled, priority: value.priority, ...(typeof value.lastChecked === "string" ? { lastChecked: value.lastChecked } : {}), ...(typeof value.lastSuccessfulUpdate === "string" ? { lastSuccessfulUpdate: value.lastSuccessfulUpdate } : {}), ...(typeof value.lastKnownGoodHash === "string" ? { lastKnownGoodHash: value.lastKnownGoodHash } : {}), ...(typeof value.lastError === "string" ? { lastError: value.lastError } : {}) };
}

function validateCollection(value: unknown, path: string, errors: string[]): DefinitionCollection | undefined {
  if (!isRecord(value) || value.schemaVersion !== 1 || typeof value.collectionId !== "string" || !ID_PATTERN.test(value.collectionId) || typeof value.name !== "string" || !value.name.trim() || !Array.isArray(value.definitions) || value.definitions.length > 1000) {
    errors.push(`${path} must be a version 1 collection with a valid ID, name, and definitions array.`); return undefined;
  }
  const definitions: SiteDefinition[] = [];
  const ids = new Set<string>();
  value.definitions.forEach((definition, index) => {
    const result = validateSiteDefinition(definition, `${path}.definitions[${index}]`);
    if (!result.valid) errors.push(...result.errors);
    else if (ids.has(result.value.id)) errors.push(`Duplicate definition ID: ${result.value.id}.`);
    else { ids.add(result.value.id); definitions.push(result.value); }
  });
  return errors.length > 0 ? undefined : { schemaVersion: 1, collectionId: value.collectionId, name: value.name.trim(), ...(typeof value.description === "string" ? { description: value.description } : {}), ...(typeof value.updatedAt === "string" ? { updatedAt: value.updatedAt } : {}), definitions };
}

function invalid(error: string): { valid: false; errors: string[] } { return { valid: false, errors: [error] }; }
