import type { ExecAsk, ExecHost, ExecSecurity } from "../../../infra/exec-approvals.js";

type ExecDirectiveParse = {
  cleaned: string;
  hasDirective: boolean;
  execHost?: ExecHost;
  execSecurity?: ExecSecurity;
  execAsk?: ExecAsk;
  execNode?: string;
  rawExecHost?: string;
  rawExecSecurity?: string;
  rawExecAsk?: string;
  rawExecNode?: string;
  hasExecOptions: boolean;
  invalidHost: boolean;
  invalidSecurity: boolean;
  invalidAsk: boolean;
  invalidNode: boolean;
};

function normalizeExecHost(value?: string): ExecHost | undefined {
  const normalized = value?.trim().toLowerCase();
  if (normalized === "sandbox" || normalized === "gateway" || normalized === "node") {
    return normalized;
  }
  return undefined;
}

function normalizeExecSecurity(value?: string): ExecSecurity | undefined {
  const normalized = value?.trim().toLowerCase();
  if (normalized === "deny" || normalized === "allowlist" || normalized === "full") {
    return normalized;
  }
  return undefined;
}

function normalizeExecAsk(value?: string): ExecAsk | undefined {
  const normalized = value?.trim().toLowerCase();
  if (normalized === "off" || normalized === "on-miss" || normalized === "always") {
    return normalized as ExecAsk;
  }
  return undefined;
}

function parseExecDirectiveArgs(raw: string): Omit<
  ExecDirectiveParse,
  "cleaned" | "hasDirective"
> & {
  consumed: number;
} {
  let i = 0;
  const len = raw.length;
  while (i < len && /\s/.test(raw[i])) {
    i += 1;
  }
  if (raw[i] === ":") {
    i += 1;
    while (i < len && /\s/.test(raw[i])) {
      i += 1;
    }
  }
  let consumed = i;
  let execHost: ExecHost | undefined;
  let execSecurity: ExecSecurity | undefined;
  let execAsk: ExecAsk | undefined;
  let execNode: string | undefined;
  let rawExecHost: string | undefined;
  let rawExecSecurity: string | undefined;
  let rawExecAsk: string | undefined;
  let rawExecNode: string | undefined;
  let hasExecOptions = false;
  let invalidHost = false;
  let invalidSecurity = false;
  let invalidAsk = false;
  let invalidNode = false;

  const takeToken = (): string | null => {
    if (i >= len) {
      return null;
    }
    const start = i;
    while (i < len && !/\s/.test(raw[i])) {
      i += 1;
    }
    if (start === i) {
      return null;
    }
    const token = raw.slice(start, i);
    while (i < len && /\s/.test(raw[i])) {
      i += 1;
    }
    return token;
  };

  const splitToken = (token: string): { key: string; value: string } | null => {
    const eq = token.indexOf("=");
    const colon = token.indexOf(":");
    const idx = eq === -1 ? colon : colon === -1 ? eq : Math.min(eq, colon);
    if (idx === -1) {
      return null;
    }
    const key = token.slice(0, idx).trim().toLowerCase();
    const value = token.slice(idx + 1).trim();
    if (!key) {
      return null;
    }
    return { key, value };
  };

  while (i < len) {
    const token = takeToken();
    if (!token) {
      break;
    }
    const parsed = splitToken(token);
    if (!parsed) {
      break;
    }
    const { key, value } = parsed;
    if (key === "host") {
      rawExecHost = value;
      execHost = normalizeExecHost(value);
      if (!execHost) {
        invalidHost = true;
      }
      hasExecOptions = true;
      consumed = i;
      continue;
    }
    if (key === "security") {
      rawExecSecurity = value;
      execSecurity = normalizeExecSecurity(value);
      if (!execSecurity) {
        invalidSecurity = true;
      }
      hasExecOptions = true;
      consumed = i;
      continue;
    }
    if (key === "ask") {
      rawExecAsk = value;
      execAsk = normalizeExecAsk(value);
      if (!execAsk) {
        invalidAsk = true;
      }
      hasExecOptions = true;
      consumed = i;
      continue;
    }
    if (key === "node") {
      rawExecNode = value;
      const trimmed = value.trim();
      if (!trimmed) {
        invalidNode = true;
      } else {
        execNode = trimmed;
      }
      hasExecOptions = true;
      consumed = i;
      continue;
    }
    break;
  }

  return {
    consumed,
    execHost,
    execSecurity,
    execAsk,
    execNode,
    rawExecHost,
    rawExecSecurity,
    rawExecAsk,
    rawExecNode,
    hasExecOptions,
    invalidHost,
    invalidSecurity,
    invalidAsk,
    invalidNode,
  };
}

export function extractExecDirective(body?: string): ExecDirectiveParse {
  if (!body) {
    return {
      cleaned: "",
      hasDirective: false,
      hasExecOptions: false,
      invalidHost: false,
      invalidSecurity: false,
      invalidAsk: false,
      invalidNode: false,
    };
  }
  const re = /(?:^|\s)\/exec(?=$|\s|:)/i;
  const match = re.exec(body);
  if (!match) {
    return {
      cleaned: body.trim(),
      hasDirective: false,
      hasExecOptions: false,
      invalidHost: false,
      invalidSecurity: false,
      invalidAsk: false,
      invalidNode: false,
    };
  }
  const start = match.index + match[0].indexOf("/exec");
  const argsStart = start + "/exec".length;
  const parsed = parseExecDirectiveArgs(body.slice(argsStart));
  const cleanedRaw = `${body.slice(0, start)} ${body.slice(argsStart + parsed.consumed)}`;
  const cleaned = cleanedRaw.replace(/\s+/g, " ").trim();
  return {
    cleaned,
    hasDirective: true,
    execHost: parsed.execHost,
    execSecurity: parsed.execSecurity,
    execAsk: parsed.execAsk,
    execNode: parsed.execNode,
    rawExecHost: parsed.rawExecHost,
    rawExecSecurity: parsed.rawExecSecurity,
    rawExecAsk: parsed.rawExecAsk,
    rawExecNode: parsed.rawExecNode,
    hasExecOptions: parsed.hasExecOptions,
    invalidHost: parsed.invalidHost,
    invalidSecurity: parsed.invalidSecurity,
    invalidAsk: parsed.invalidAsk,
    invalidNode: parsed.invalidNode,
  };
}
