import { isPilotDeckHookEvent } from "../protocol/events.js";
import type {
  PilotDeckHookCommand,
  PilotDeckHookMatcher,
  PilotDeckHooksSettings,
} from "../protocol/settings.js";

export type ParseHooksConfigResult = {
  settings: PilotDeckHooksSettings;
  diagnostics: string[];
};

export function parseHooksConfig(raw: unknown): ParseHooksConfigResult {
  const diagnostics: string[] = [];
  const settings: PilotDeckHooksSettings = {};

  if (raw === undefined || raw === null) {
    return { settings, diagnostics };
  }
  if (!isRecord(raw)) {
    return { settings, diagnostics: ["Hooks config must be an object."] };
  }

  for (const [eventName, rawMatchers] of Object.entries(raw)) {
    if (!isPilotDeckHookEvent(eventName)) {
      diagnostics.push(`Unsupported hook event ${eventName}.`);
      continue;
    }
    if (!Array.isArray(rawMatchers)) {
      diagnostics.push(`Hook event ${eventName} must contain an array of matchers.`);
      continue;
    }

    const matchers: PilotDeckHookMatcher[] = [];
    for (const rawMatcher of rawMatchers) {
      const matcher = parseMatcher(eventName, rawMatcher, diagnostics);
      if (matcher) {
        matchers.push(matcher);
      }
    }
    if (matchers.length > 0) {
      settings[eventName] = matchers;
    }
  }

  return { settings, diagnostics };
}

function parseMatcher(eventName: string, rawMatcher: unknown, diagnostics: string[]): PilotDeckHookMatcher | undefined {
  if (!isRecord(rawMatcher)) {
    diagnostics.push(`Hook matcher for ${eventName} must be an object.`);
    return undefined;
  }
  if (!Array.isArray(rawMatcher.hooks)) {
    diagnostics.push(`Hook matcher for ${eventName} must contain hooks array.`);
    return undefined;
  }

  const hooks: PilotDeckHookCommand[] = [];
  for (const rawHook of rawMatcher.hooks) {
    const hook = parseHookCommand(eventName, rawHook, diagnostics);
    if (hook) {
      hooks.push(hook);
    }
  }

  return {
    matcher: typeof rawMatcher.matcher === "string" ? rawMatcher.matcher : undefined,
    pluginName: typeof rawMatcher.pluginName === "string" ? rawMatcher.pluginName : undefined,
    pluginId: typeof rawMatcher.pluginId === "string" ? rawMatcher.pluginId : undefined,
    pluginRoot: typeof rawMatcher.pluginRoot === "string" ? rawMatcher.pluginRoot : undefined,
    hooks,
  };
}

function parseHookCommand(eventName: string, rawHook: unknown, diagnostics: string[]): PilotDeckHookCommand | undefined {
  if (!isRecord(rawHook) || typeof rawHook.type !== "string") {
    diagnostics.push(`Hook for ${eventName} must contain a type.`);
    return undefined;
  }

  const common = {
    if: stringOrUndefined(rawHook.if),
    statusMessage: stringOrUndefined(rawHook.statusMessage),
    once: booleanOrUndefined(rawHook.once),
    timeout: numberOrUndefined(rawHook.timeout),
  };

  switch (rawHook.type) {
    case "command":
      if (typeof rawHook.command !== "string") {
        diagnostics.push(`Command hook for ${eventName} must contain command.`);
        return undefined;
      }
      return {
        type: "command",
        command: rawHook.command,
        shell: rawHook.shell === "powershell" ? "powershell" : rawHook.shell === "bash" ? "bash" : undefined,
        async: booleanOrUndefined(rawHook.async),
        asyncRewake: booleanOrUndefined(rawHook.asyncRewake),
        ...common,
      };
    case "prompt":
      if (typeof rawHook.prompt !== "string") {
        diagnostics.push(`Prompt hook for ${eventName} must contain prompt.`);
        return undefined;
      }
      return { type: "prompt", prompt: rawHook.prompt, model: stringOrUndefined(rawHook.model), ...common };
    case "http":
      if (typeof rawHook.url !== "string") {
        diagnostics.push(`HTTP hook for ${eventName} must contain url.`);
        return undefined;
      }
      return {
        type: "http",
        url: rawHook.url,
        headers: isRecord(rawHook.headers) ? stringifyRecord(rawHook.headers) : undefined,
        allowedEnvVars: Array.isArray(rawHook.allowedEnvVars)
          ? rawHook.allowedEnvVars.filter((value): value is string => typeof value === "string")
          : undefined,
        ...common,
      };
    case "agent":
      if (typeof rawHook.prompt !== "string") {
        diagnostics.push(`Agent hook for ${eventName} must contain prompt.`);
        return undefined;
      }
      return { type: "agent", prompt: rawHook.prompt, model: stringOrUndefined(rawHook.model), ...common };
    case "callback":
      diagnostics.push(`Callback hook for ${eventName} is runtime-only and cannot be loaded from persistent config.`);
      return undefined;
    default:
      diagnostics.push(`Unsupported hook type ${rawHook.type} for ${eventName}.`);
      return undefined;
  }
}

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

function stringOrUndefined(value: unknown): string | undefined {
  return typeof value === "string" ? value : undefined;
}

function booleanOrUndefined(value: unknown): boolean | undefined {
  return typeof value === "boolean" ? value : undefined;
}

function numberOrUndefined(value: unknown): number | undefined {
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
}

function stringifyRecord(record: Record<string, unknown>): Record<string, string> {
  return Object.fromEntries(
    Object.entries(record).filter((entry): entry is [string, string] => typeof entry[1] === "string"),
  );
}