import { isRecord, parseDotPath } from "./shared.js";
import type { SecretTargetRegistryEntry } from "./target-registry-types.js";

export type PathPatternToken =
  | { kind: "literal"; value: string }
  | { kind: "wildcard" }
  | { kind: "array"; field: string };

export type CompiledTargetRegistryEntry = SecretTargetRegistryEntry & {
  pathTokens: PathPatternToken[];
  pathDynamicTokenCount: number;
  refPathTokens?: PathPatternToken[];
  refPathDynamicTokenCount: number;
};

export type ExpandedPathMatch = {
  segments: string[];
  captures: string[];
  value: unknown;
};

function countDynamicPatternTokens(tokens: PathPatternToken[]): number {
  return tokens.filter((token) => token.kind === "wildcard" || token.kind === "array").length;
}

export function parsePathPattern(pathPattern: string): PathPatternToken[] {
  const segments = parseDotPath(pathPattern);
  return segments.map((segment) => {
    if (segment === "*") {
      return { kind: "wildcard" } as const;
    }
    if (segment.endsWith("[]")) {
      const field = segment.slice(0, -2).trim();
      if (!field) {
        throw new Error(`Invalid target path pattern: ${pathPattern}`);
      }
      return { kind: "array", field } as const;
    }
    return { kind: "literal", value: segment } as const;
  });
}

export function compileTargetRegistryEntry(
  entry: SecretTargetRegistryEntry,
): CompiledTargetRegistryEntry {
  const pathTokens = parsePathPattern(entry.pathPattern);
  const pathDynamicTokenCount = countDynamicPatternTokens(pathTokens);
  const refPathTokens = entry.refPathPattern ? parsePathPattern(entry.refPathPattern) : undefined;
  const refPathDynamicTokenCount = refPathTokens ? countDynamicPatternTokens(refPathTokens) : 0;
  const requiresSiblingRefPath = entry.secretShape === "sibling_ref"; // pragma: allowlist secret
  if (requiresSiblingRefPath && !refPathTokens) {
    throw new Error(`Missing refPathPattern for sibling_ref target: ${entry.id}`);
  }
  if (refPathTokens && refPathDynamicTokenCount !== pathDynamicTokenCount) {
    throw new Error(`Mismatched wildcard shape for target ref path: ${entry.id}`);
  }
  return {
    ...entry,
    pathTokens,
    pathDynamicTokenCount,
    refPathTokens,
    refPathDynamicTokenCount,
  };
}

export function matchPathTokens(
  segments: string[],
  tokens: PathPatternToken[],
): {
  captures: string[];
} | null {
  const captures: string[] = [];
  let index = 0;
  for (const token of tokens) {
    if (token.kind === "literal") {
      if (segments[index] !== token.value) {
        return null;
      }
      index += 1;
      continue;
    }
    if (token.kind === "wildcard") {
      const value = segments[index];
      if (!value) {
        return null;
      }
      captures.push(value);
      index += 1;
      continue;
    }
    if (segments[index] !== token.field) {
      return null;
    }
    const next = segments[index + 1];
    if (!next || !/^\d+$/.test(next)) {
      return null;
    }
    captures.push(next);
    index += 2;
  }
  return index === segments.length ? { captures } : null;
}

export function materializePathTokens(
  tokens: PathPatternToken[],
  captures: string[],
): string[] | null {
  const out: string[] = [];
  let captureIndex = 0;
  for (const token of tokens) {
    if (token.kind === "literal") {
      out.push(token.value);
      continue;
    }
    if (token.kind === "wildcard") {
      const value = captures[captureIndex];
      if (!value) {
        return null;
      }
      out.push(value);
      captureIndex += 1;
      continue;
    }
    const arrayIndex = captures[captureIndex];
    if (!arrayIndex || !/^\d+$/.test(arrayIndex)) {
      return null;
    }
    out.push(token.field, arrayIndex);
    captureIndex += 1;
  }
  return captureIndex === captures.length ? out : null;
}

export function expandPathTokens(root: unknown, tokens: PathPatternToken[]): ExpandedPathMatch[] {
  const out: ExpandedPathMatch[] = [];
  const walk = (
    node: unknown,
    tokenIndex: number,
    segments: string[],
    captures: string[],
  ): void => {
    const token = tokens[tokenIndex];
    if (!token) {
      out.push({ segments, captures, value: node });
      return;
    }
    const isLeaf = tokenIndex === tokens.length - 1;

    if (token.kind === "literal") {
      if (!isRecord(node)) {
        return;
      }
      if (isLeaf) {
        out.push({
          segments: [...segments, token.value],
          captures,
          value: node[token.value],
        });
        return;
      }
      if (!Object.prototype.hasOwnProperty.call(node, token.value)) {
        return;
      }
      walk(node[token.value], tokenIndex + 1, [...segments, token.value], captures);
      return;
    }

    if (token.kind === "wildcard") {
      if (!isRecord(node)) {
        return;
      }
      for (const [key, value] of Object.entries(node)) {
        if (isLeaf) {
          out.push({
            segments: [...segments, key],
            captures: [...captures, key],
            value,
          });
          continue;
        }
        walk(value, tokenIndex + 1, [...segments, key], [...captures, key]);
      }
      return;
    }

    if (!isRecord(node)) {
      return;
    }
    const items = node[token.field];
    if (!Array.isArray(items)) {
      return;
    }
    for (let index = 0; index < items.length; index += 1) {
      const item = items[index];
      const indexString = String(index);
      if (isLeaf) {
        out.push({
          segments: [...segments, token.field, indexString],
          captures: [...captures, indexString],
          value: item,
        });
        continue;
      }
      walk(
        item,
        tokenIndex + 1,
        [...segments, token.field, indexString],
        [...captures, indexString],
      );
    }
  };
  walk(root, 0, [], []);
  return out;
}
