import { expandHomePath } from "#src/expand-home";
import { canonicalizePath } from "#src/path/canonicalize-path";
import type { PathFlavor } from "#src/path/path-flavor";

/**
 * Representation derivation backing {@link AccessPath}: turn an accessed path
 * into the lexical / canonical / policy-value forms the resolver matches
 * against rules. Pure (no filesystem access except `canonicalizePath`'s
 * best-effort symlink resolution); the platform's path semantics arrive as an
 * injected {@link PathFlavor}, never read ambiently.
 */
export function normalizePathForComparison(
  pathValue: string,
  base: string,
  flavor: PathFlavor,
): string {
  const cleaned = normalizePathPolicyLiteral(pathValue);
  return cleaned ? flavor.comparable(cleaned, base) : "";
}

export interface PathPolicyValueOptions {
  /**
   * Current Pi working directory. When provided, returned values include a
   * project-relative alias for paths that resolve inside this directory.
   */
  cwd?: string;
  /**
   * Directory used to resolve `pathValue` into an absolute policy value.
   * Defaults to `cwd`. Bash uses this for tokens seen after a literal `cd`.
   */
  resolveBase?: string;
}

/**
 * Normalize a single path-like lookup value without resolving it against CWD.
 *
 * Preserves compatibility with existing relative path rules (`src/*`, `*.env`)
 * while applying the lexical cleanup {@link normalizePathForComparison} shares:
 * trim, strip simple wrapping quotes, strip the OpenCode-style leading `@`, and
 * expand `~` / `$HOME`.
 */
export function normalizePathPolicyLiteral(pathValue: string): string {
  const trimmed = pathValue.trim().replace(/^['"]|['"]$/g, "");
  if (!trimmed) return "";
  const unprefixed = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
  return expandHomePath(unprefixed);
}

/**
 * Return equivalent lookup values for path-policy matching.
 *
 * The first value is the cwd/effective-base normalized absolute path when a
 * base is available. The later values preserve project-relative and raw
 * relative forms so existing rules like `src/*` and `*.env` continue to match.
 */
export function getPathPolicyValues(
  pathValue: string,
  options: PathPolicyValueOptions,
  flavor: PathFlavor,
): string[] {
  const literal = normalizePathPolicyLiteral(pathValue);
  if (!literal) return [];
  if (literal === "*") return ["*"];

  return [
    ...new Set([
      ...getAbsolutePathPolicyValues(pathValue, options, flavor),
      literal,
    ]),
  ];
}

function getAbsolutePathPolicyValues(
  pathValue: string,
  options: PathPolicyValueOptions,
  flavor: PathFlavor,
): string[] {
  const resolveBase = options.resolveBase ?? options.cwd;
  if (!resolveBase) return [];

  const absolute = normalizePathForComparison(pathValue, resolveBase, flavor);
  if (!absolute) return [];

  return [
    absolute,
    ...getCwdRelativePathPolicyValues(absolute, options.cwd, flavor),
  ];
}

function getCwdRelativePathPolicyValues(
  absolute: string,
  cwd: string | undefined,
  flavor: PathFlavor,
): string[] {
  if (!cwd) return [];

  const normalizedCwd = normalizePathForComparison(cwd, cwd, flavor);
  if (!normalizedCwd) return [];
  if (absolute !== normalizedCwd && !flavor.isWithin(absolute, normalizedCwd)) {
    return [];
  }

  const relativeValue = flavor.impl.relative(normalizedCwd, absolute);
  return relativeValue ? [relativeValue] : [];
}

/**
 * Like {@link normalizePathForComparison} but also resolves symlinks via
 * `realpathSync` (best-effort). Use this for containment decisions where the
 * OS-followed path matters, not for pattern matching.
 */
export function canonicalNormalizePathForComparison(
  pathValue: string,
  base: string,
  flavor: PathFlavor,
): string {
  const lexical = normalizePathForComparison(pathValue, base, flavor);
  if (!lexical) return "";
  return flavor.fold(canonicalizePath(lexical, flavor));
}
