import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
import * as fs from "node:fs";
import * as path from "node:path";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

type ResourceKind = "skill" | "ext";

interface PackageFilter {
  source: string;
  extensions?: string[];
  skills?: string[];
}

interface PiSettings {
  packages?: (string | PackageFilter)[];
  [key: string]: unknown;
}

// ---------------------------------------------------------------------------
// Paths
// ---------------------------------------------------------------------------

const AGENTS_DIR = path.join(process.env.HOME!, ".agents");

function projectSettingsPath(cwd: string): string {
  return path.join(cwd, ".pi", "settings.json");
}

/** Find the package filter entry that has source pointing to ~/.agents */
function findAgentsPackage(settings: PiSettings): PackageFilter | undefined {
  const pkgs = settings.packages ?? [];
  return pkgs.find(
    (p): p is PackageFilter =>
      typeof p === "object" && p.source === AGENTS_DIR,
  );
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function kindToDir(kind: ResourceKind): string {
  return kind === "skill" ? "skills" : "extensions";
}

/** List available names under ~/.agents/<skills|extensions>/ */
function listAvailable(kind: ResourceKind): string[] {
  const dir = path.join(AGENTS_DIR, kindToDir(kind));
  if (!fs.existsSync(dir)) return [];
  return fs
    .readdirSync(dir, { withFileTypes: true })
    .filter((d) => d.isDirectory())
    .map((d) => d.name)
    .sort();
}

/**
 * Resolve the filter path for an extension under ~/.agents/extensions/<name>/
 * by inspecting its structure (package.json pi manifest, or just index.ts).
 * Returns undefined if the directory doesn't exist or has no loadable entry.
 */
function resolveExtensionPaths(name: string): string[] | undefined {
  const extDir = path.join(AGENTS_DIR, "extensions", name);
  if (!fs.existsSync(extDir) || !fs.statSync(extDir).isDirectory()) return undefined;

  const pkgJsonPath = path.join(extDir, "package.json");
  if (fs.existsSync(pkgJsonPath)) {
    try {
      const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
      const piExts: string[] | undefined = pkg?.pi?.extensions;
      if (Array.isArray(piExts) && piExts.length > 0) {
        // Strip leading ./ and prefix with extensions/<name>/
        return piExts.map((ep: string) =>
          path.posix.join("extensions", name, ep.replace(/^\.\//, "")),
        );
      }
    } catch {
      // fall through
    }
  }

  // No pi manifest — look for index.ts or index.js
  for (const fn of ["index.ts", "index.js"]) {
    if (fs.existsSync(path.join(extDir, fn))) {
      return [path.posix.join("extensions", name, fn)];
    }
  }

  return undefined;
}

/** Resolve the filter path for a skill: "skills/<name>" */
function resolveSkillPath(name: string): string | undefined {
  const skillDir = path.join(AGENTS_DIR, "skills", name);
  if (!fs.existsSync(skillDir) || !fs.statSync(skillDir).isDirectory()) return undefined;
  return `skills/${name}`;
}

function loadSettings(cwd: string): PiSettings {
  const p = projectSettingsPath(cwd);
  if (!fs.existsSync(p)) return {};
  try {
    return JSON.parse(fs.readFileSync(p, "utf-8")) as PiSettings;
  } catch {
    return {};
  }
}

function saveSettings(cwd: string, settings: PiSettings): void {
  const dir = path.dirname(projectSettingsPath(cwd));
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(projectSettingsPath(cwd), JSON.stringify(settings, null, 2) + "\n");
}

/** Ensure the agents package entry exists, creating it if missing. Always ensures extensions/skills arrays exist to block auto-discovery. */
function ensureAgentsPackage(settings: PiSettings): PackageFilter {
  const existing = findAgentsPackage(settings);
  if (existing) {
    existing.extensions ??= [];
    existing.skills ??= [];
    return existing;
  }

  const entry: PackageFilter = { source: AGENTS_DIR, extensions: [], skills: [] };
  const pkgs = settings.packages ?? [];
  pkgs.push(entry);
  settings.packages = pkgs;
  return entry;
}

// ---------------------------------------------------------------------------
// Extension
// ---------------------------------------------------------------------------

export default function (pi: ExtensionAPI) {
  // ── /config-add skill|ext <name> ──────────────────────────────
  pi.registerCommand("config-add", {
    description:
      "Add a skill or extension from ~/.agents to this project. Usage: /config-add skill <name>  |  /config-add ext <name>",
    handler: async (args, ctx: ExtensionCommandContext) => {
      const parts = (args ?? "").trim().split(/\s+/);
      if (parts.length < 2) {
        ctx.ui.notify("Usage: /config-add skill <name>  |  /config-add ext <name>", "error");
        return;
      }
      const [kindStr, name] = parts as [string, string];
      if (kindStr !== "skill" && kindStr !== "ext") {
        ctx.ui.notify("First arg must be 'skill' or 'ext'", "error");
        return;
      }
      const kind: ResourceKind = kindStr;

      // Check existence + resolve path(s)
      const available = listAvailable(kind);
      if (!available.includes(name)) {
        ctx.ui.notify(
          `${kind} '${name}' not found in ~/.agents/${kindToDir(kind)}/. Available: ${available.join(", ") || "(none)"}`,
          "error",
        );
        return;
      }

      let resolvedPaths: string[];
      if (kind === "skill") {
        const p = resolveSkillPath(name);
        if (!p) {
          ctx.ui.notify(`Could not resolve path for skill '${name}'`, "error");
          return;
        }
        resolvedPaths = [p];
      } else {
        const paths = resolveExtensionPaths(name);
        if (!paths || paths.length === 0) {
          ctx.ui.notify(
            `Could not resolve entry point for extension '${name}'. Check ~/.agents/extensions/${name}/ for structure.`,
            "error",
          );
          return;
        }
        resolvedPaths = paths;
      }

      const settings = loadSettings(ctx.cwd);
      const pkg = ensureAgentsPackage(settings);
      const key = kind === "skill" ? "skills" : "extensions";
      const arr: string[] = (pkg[key] as string[]) ?? [];

      const alreadyPresent = resolvedPaths.every((rp) => arr.includes(rp));
      if (alreadyPresent) {
        ctx.ui.notify(`${kind} '${name}' is already active.`, "info");
        return;
      }

      // Add any that aren't already there
      for (const rp of resolvedPaths) {
        if (!arr.includes(rp)) arr.push(rp);
      }
      pkg[key] = arr;
      saveSettings(ctx.cwd, settings);
      const pathsStr = resolvedPaths.join(", ");
      ctx.ui.notify(`✓ Added ${kind} '${name}' (${pathsStr}). Run /reload to apply.`, "success");
    },
  });

  // ── /config-remove skill|ext <name> ──────────────────────────
  pi.registerCommand("config-remove", {
    description:
      "Remove a skill or extension from this project. Usage: /config-remove skill <name>  |  /config-remove ext <name>",
    handler: async (args, ctx: ExtensionCommandContext) => {
      const parts = (args ?? "").trim().split(/\s+/);
      if (parts.length < 2) {
        ctx.ui.notify("Usage: /config-remove skill <name>  |  /config-remove ext <name>", "error");
        return;
      }
      const [kindStr, name] = parts as [string, string];
      if (kindStr !== "skill" && kindStr !== "ext") {
        ctx.ui.notify("First arg must be 'skill' or 'ext'", "error");
        return;
      }
      const kind: ResourceKind = kindStr;

      const settings = loadSettings(ctx.cwd);
      const pkg = findAgentsPackage(settings);
      if (!pkg) {
        ctx.ui.notify("No ~/.agents package entry found in settings.", "error");
        return;
      }

      const key = kind === "skill" ? "skills" : "extensions";
      const arr: string[] = (pkg[key] as string[]) ?? [];

      // Remove any entry that contains "/<name>/" or "/<name>." or ends with "/<name>"
      // This handles: "skills/caveman", "extensions/pi-config/index.ts", etc.
      const filtered = arr.filter((entry) => {
        const parts2 = entry.split("/");
        return !parts2.includes(name);
      });

      if (filtered.length === arr.length) {
        ctx.ui.notify(`${kind} '${name}' was not found in the active list.`, "info");
        return;
      }

      pkg[key] = filtered;
      saveSettings(ctx.cwd, settings);
      ctx.ui.notify(`✓ Removed ${kind} '${name}'. Run /reload to apply.`, "success");
    },
  });

  // ── /config-show ─────────────────────────────────────────────
  pi.registerCommand("config-show", {
    description: "Show which skills and extensions are active in this project",
    handler: async (_args, ctx: ExtensionCommandContext) => {
      const settings = loadSettings(ctx.cwd);
      const pkg = findAgentsPackage(settings);

      const availableSkills = listAvailable("skill");
      const availableExts = listAvailable("ext");

      const activeSkills: string[] = pkg?.skills ?? [];
      const activeExts: string[] = pkg?.extensions ?? [];

      // Derive names from paths
      function namesFromPaths(paths: string[], kind: ResourceKind): string[] {
        const prefix = kindToDir(kind) + "/";
        return paths
          .filter((p) => p.startsWith(prefix))
          .map((p) => p.slice(prefix.length).split("/")[0])
          .filter((n, i, a) => a.indexOf(n) === i)
          .sort();
      }

      const includedSkills = namesFromPaths(activeSkills, "skill");
      const includedExts = namesFromPaths(activeExts, "ext");

      const allSkills = availableSkills
        .map((s) => (includedSkills.includes(s) ? `  ✓ ${s}` : `    ${s}`))
        .join("\n");
      const allExts = availableExts
        .map((e) => (includedExts.includes(e) ? `  ✓ ${e}` : `    ${e}`))
        .join("\n");

      const output = [
        "── Skills in ~/.agents/skills/ ──",
        allSkills || "  (none)",
        "",
        "── Extensions in ~/.agents/extensions/ ──",
        allExts || "  (none)",
        "",
        `Settings: ${projectSettingsPath(ctx.cwd)}`,
        "",
        "Use /config-add <skill|ext> <name> and /config-remove <skill|ext> <name>",
      ];

      ctx.ui.notify(output.join("\n"), "info");
    },
  });

  // ── /config-list ────────────────────────────────────────────
  pi.registerCommand("config-list", {
    description: "List all available skills and extensions in ~/.agents",
    handler: async (_args, ctx: ExtensionCommandContext) => {
      const skills = listAvailable("skill");
      const exts = listAvailable("ext");
      const output = [
        `Skills (~/.agents/skills/): ${skills.length > 0 ? skills.join(", ") : "(none)"}`,
        `Extensions (~/.agents/extensions/): ${exts.length > 0 ? exts.join(", ") : "(none)"}`,
      ];
      ctx.ui.notify(output.join("\n"), "info");
    },
  });

  // ── /config-setup ───────────────────────────────────────────
  pi.registerCommand("config-setup", {
    description: "Set up this folder with .pi, settings.json, and memory vault. Creates what's missing and sets MEMCTX_PACKS_PATH for the current session.",
    handler: async (_args, ctx: ExtensionCommandContext) => {
      const cwd = ctx.cwd;
      const piDir = path.join(cwd, ".pi");
      const settingsPath = path.join(piDir, "settings.json");
      const memoryPacksDir = path.join(piDir, "memory-vault", "packs");

      const created: string[] = [];
      const existed: string[] = [];

      // 1. .pi/
      if (!fs.existsSync(piDir)) {
        fs.mkdirSync(piDir, { recursive: true });
        created.push(".pi/");
      } else {
        existed.push(".pi/");
      }

      // 2. settings.json — load or create, ensure ~/.agents package
      let settings: PiSettings;
      if (fs.existsSync(settingsPath)) {
        try {
          settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
        } catch {
          settings = {};
        }
        existed.push(".pi/settings.json");
      } else {
        settings = {};
        created.push(".pi/settings.json");
      }
      ensureAgentsPackage(settings);

      // Pre-populate default extensions and skills for new projects
      const pkg = ensureAgentsPackage(settings);
      const defaultExts = [
        "extensions/pi-config/index.ts",
        "extensions/filechanges/index.ts",
        "extensions/ask-user-question/index.ts",
      ];
      const defaultSkills = ["skills/obsidian-cli", "skills/npm-security"];
      for (const d of defaultExts) {
        if (!pkg.extensions!.includes(d)) pkg.extensions!.push(d);
      }
      for (const d of defaultSkills) {
        if (!pkg.skills!.includes(d)) pkg.skills!.push(d);
      }
      saveSettings(cwd, settings);

      // 3. memory-vault/packs/
      if (!fs.existsSync(memoryPacksDir)) {
        fs.mkdirSync(memoryPacksDir, { recursive: true });
        created.push(".pi/memory-vault/packs/");
      } else {
        existed.push(".pi/memory-vault/packs/");
      }

      // 4. Seed a minimal pack so pi-memctx finds this vault (needs at least one pack to exist)
      const slug = path.basename(cwd).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "workspace";
      const seedPackDir = path.join(memoryPacksDir, slug, "00-system", "pi-agent");
      if (!fs.existsSync(seedPackDir)) {
        fs.mkdirSync(seedPackDir, { recursive: true });
        const today = new Date().toISOString().slice(0, 10);
        const title = slug.replace(/-/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase());
        const manifest = `---
type: system
id: system.${slug}.memory-manifest
title: ${title} Memory Manifest
status: active
source_of_truth: true
freshness: current
last_reviewed: ${today}
tags:
  - agent-memory/system
  - pack/${slug}
---

# ${title} Memory Manifest

This pack stores safe durable context generated from \`${cwd}\`.

## Retrieval Protocol

See [[packs/${slug}/00-system/pi-agent/retrieval-protocol|Retrieval Protocol]] and [[packs/${slug}/00-system/pi-agent/resource-map|Resource Map]].
`;
        fs.writeFileSync(path.join(seedPackDir, "memory-manifest.md"), manifest);
        created.push(".pi/memory-vault/packs/" + slug + "/");
      }

      // 5. Set env var so pi-memctx uses this local vault for the session
      process.env.MEMCTX_PACKS_PATH = memoryPacksDir;

      const lines = [
        created.length > 0
          ? `\u2713 Created: ${created.join(", ")}`
          : "",
        existed.length > 0
          ? `  Already existed: ${existed.join(", ")}`
          : "",
        "",
        `MEMCTX_PACKS_PATH set to:`,
        `  ${memoryPacksDir}`,
        "",
        created.length > 0 ? "Next: run  /memctx-init  in this folder to create the memory pack." : "",
      ].filter(Boolean).join("\n");

      ctx.ui.notify(lines, "success");
    },
  });
}
