import path from "node:path";

import type { ClawdisConfig } from "../config/config.js";
import { CONFIG_DIR } from "../utils.js";
import {
  hasBinary,
  isConfigPathTruthy,
  loadWorkspaceSkillEntries,
  resolveConfigPath,
  resolveSkillConfig,
  resolveSkillsInstallPreferences,
  type SkillEntry,
  type SkillInstallSpec,
  type SkillsInstallPreferences,
} from "./skills.js";

export type SkillStatusConfigCheck = {
  path: string;
  value: unknown;
  satisfied: boolean;
};

export type SkillInstallOption = {
  id: string;
  kind: SkillInstallSpec["kind"];
  label: string;
  bins: string[];
};

export type SkillStatusEntry = {
  name: string;
  description: string;
  source: string;
  filePath: string;
  baseDir: string;
  skillKey: string;
  primaryEnv?: string;
  emoji?: string;
  homepage?: string;
  always: boolean;
  disabled: boolean;
  eligible: boolean;
  requirements: {
    bins: string[];
    env: string[];
    config: string[];
  };
  missing: {
    bins: string[];
    env: string[];
    config: string[];
  };
  configChecks: SkillStatusConfigCheck[];
  install: SkillInstallOption[];
};

export type SkillStatusReport = {
  workspaceDir: string;
  managedSkillsDir: string;
  skills: SkillStatusEntry[];
};

function resolveSkillKey(entry: SkillEntry): string {
  return entry.clawdis?.skillKey ?? entry.skill.name;
}

function selectPreferredInstallSpec(
  install: SkillInstallSpec[],
  prefs: SkillsInstallPreferences,
): { spec: SkillInstallSpec; index: number } | undefined {
  if (install.length === 0) return undefined;
  const indexed = install.map((spec, index) => ({ spec, index }));
  const findKind = (kind: SkillInstallSpec["kind"]) =>
    indexed.find((item) => item.spec.kind === kind);

  const brewSpec = findKind("brew");
  const nodeSpec = findKind("node");
  const goSpec = findKind("go");
  const uvSpec = findKind("uv");

  if (prefs.preferBrew && hasBinary("brew") && brewSpec) return brewSpec;
  if (uvSpec) return uvSpec;
  if (nodeSpec) return nodeSpec;
  if (brewSpec) return brewSpec;
  if (goSpec) return goSpec;
  return indexed[0];
}

function normalizeInstallOptions(
  entry: SkillEntry,
  prefs: SkillsInstallPreferences,
): SkillInstallOption[] {
  const install = entry.clawdis?.install ?? [];
  if (install.length === 0) return [];
  const preferred = selectPreferredInstallSpec(install, prefs);
  if (!preferred) return [];
  const { spec, index } = preferred;
  const id = (spec.id ?? `${spec.kind}-${index}`).trim();
  const bins = spec.bins ?? [];
  let label = (spec.label ?? "").trim();
  if (spec.kind === "node" && spec.package) {
    label = `Install ${spec.package} (${prefs.nodeManager})`;
  }
  if (!label) {
    if (spec.kind === "brew" && spec.formula) {
      label = `Install ${spec.formula} (brew)`;
    } else if (spec.kind === "node" && spec.package) {
      label = `Install ${spec.package} (${prefs.nodeManager})`;
    } else if (spec.kind === "go" && spec.module) {
      label = `Install ${spec.module} (go)`;
    } else if (spec.kind === "uv" && spec.package) {
      label = `Install ${spec.package} (uv)`;
    } else {
      label = "Run installer";
    }
  }
  return [
    {
      id,
      kind: spec.kind,
      label,
      bins,
    },
  ];
}

function buildSkillStatus(
  entry: SkillEntry,
  config?: ClawdisConfig,
  prefs?: SkillsInstallPreferences,
): SkillStatusEntry {
  const skillKey = resolveSkillKey(entry);
  const skillConfig = resolveSkillConfig(config, skillKey);
  const disabled = skillConfig?.enabled === false;
  const always = entry.clawdis?.always === true;
  const emoji = entry.clawdis?.emoji ?? entry.frontmatter.emoji;
  const homepageRaw =
    entry.clawdis?.homepage ??
    entry.frontmatter.homepage ??
    entry.frontmatter.website ??
    entry.frontmatter.url;
  const homepage = homepageRaw?.trim() ? homepageRaw.trim() : undefined;

  const requiredBins = entry.clawdis?.requires?.bins ?? [];
  const requiredEnv = entry.clawdis?.requires?.env ?? [];
  const requiredConfig = entry.clawdis?.requires?.config ?? [];

  const missingBins = requiredBins.filter((bin) => !hasBinary(bin));

  const missingEnv: string[] = [];
  for (const envName of requiredEnv) {
    if (process.env[envName]) continue;
    if (skillConfig?.env?.[envName]) continue;
    if (skillConfig?.apiKey && entry.clawdis?.primaryEnv === envName) {
      continue;
    }
    missingEnv.push(envName);
  }

  const configChecks: SkillStatusConfigCheck[] = requiredConfig.map(
    (pathStr) => {
      const value = resolveConfigPath(config, pathStr);
      const satisfied = isConfigPathTruthy(config, pathStr);
      return { path: pathStr, value, satisfied };
    },
  );
  const missingConfig = configChecks
    .filter((check) => !check.satisfied)
    .map((check) => check.path);

  const missing = always
    ? { bins: [], env: [], config: [] }
    : { bins: missingBins, env: missingEnv, config: missingConfig };
  const eligible =
    !disabled &&
    (always ||
      (missing.bins.length === 0 &&
        missing.env.length === 0 &&
        missing.config.length === 0));

  return {
    name: entry.skill.name,
    description: entry.skill.description,
    source: entry.skill.source,
    filePath: entry.skill.filePath,
    baseDir: entry.skill.baseDir,
    skillKey,
    primaryEnv: entry.clawdis?.primaryEnv,
    emoji,
    homepage,
    always,
    disabled,
    eligible,
    requirements: {
      bins: requiredBins,
      env: requiredEnv,
      config: requiredConfig,
    },
    missing,
    configChecks,
    install: normalizeInstallOptions(
      entry,
      prefs ?? resolveSkillsInstallPreferences(config),
    ),
  };
}

export function buildWorkspaceSkillStatus(
  workspaceDir: string,
  opts?: {
    config?: ClawdisConfig;
    managedSkillsDir?: string;
    entries?: SkillEntry[];
  },
): SkillStatusReport {
  const managedSkillsDir =
    opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
  const skillEntries =
    opts?.entries ?? loadWorkspaceSkillEntries(workspaceDir, opts);
  const prefs = resolveSkillsInstallPreferences(opts?.config);
  return {
    workspaceDir,
    managedSkillsDir,
    skills: skillEntries.map((entry) =>
      buildSkillStatus(entry, opts?.config, prefs),
    ),
  };
}
