import type { Plugin } from '@opencode-ai/plugin';
import { tool } from '@opencode-ai/plugin';
import { existsSync } from 'fs';
import fs from 'fs/promises';
import os from 'os';
import path from 'path';

interface CommandFrontmatter {
  description?: string;
  agent?: string;
  model?: string;
  subtask?: boolean;
}

interface ParsedCommand {
  name: string;
  frontmatter: CommandFrontmatter;
  template: string;
}

type FeatureStatus = 'active' | 'blocked' | 'ready' | 'pushed';

interface FeatureState {
  feature: string;
  channel?: string;
  workspace: string;
  agent?: string;
  status: FeatureStatus;
  lastSyncedTrunk?: string;
  lastPush?: string;
}

interface ManagerState {
  root: string;
  trunkChannel: string;
  remote?: string;
  features: Record<string, FeatureState>;
}

interface PijulManagerArgs {
  action: 'init' | 'spawn' | 'sync' | 'record' | 'push' | 'status' | 'close';
  feature?: string;
  trunk?: string;
  remote?: string;
  workspaceRoot?: string;
  agent?: string;
  message?: string;
}

export function parseFrontmatter(content: string): { frontmatter: CommandFrontmatter; body: string } {
  const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
  const match = content.match(frontmatterRegex);

  if (!match) {
    return { frontmatter: {}, body: content.trim() };
  }

  const [, yamlContent, body] = match;
  const frontmatter: CommandFrontmatter = {};

  for (const line of yamlContent.split('\n')) {
    const colonIndex = line.indexOf(':');
    if (colonIndex === -1) continue;

    const key = line.slice(0, colonIndex).trim();
    const value = line.slice(colonIndex + 1).trim();

    if (key === 'description') frontmatter.description = value;
    if (key === 'agent') frontmatter.agent = value;
    if (key === 'model') frontmatter.model = value;
    if (key === 'subtask') frontmatter.subtask = value === 'true';
  }

  return { frontmatter, body: body.trim() };
}

export async function loadCommands(): Promise<ParsedCommand[]> {
  const commands: ParsedCommand[] = [];
  let commandDir = path.join(import.meta.dir, 'command');
  if (!existsSync(commandDir)) {
    const fallbackDir = path.join(import.meta.dir, '..', 'src', 'command');
    if (existsSync(fallbackDir)) {
      commandDir = fallbackDir;
    } else {
      return commands;
    }
  }
  const glob = new Bun.Glob('**/*.md');

  for await (const file of glob.scan({ cwd: commandDir, absolute: true })) {
    const content = await Bun.file(file).text();
    const { frontmatter, body } = parseFrontmatter(content);
    const relativePath = path.relative(commandDir, file);
    const name = relativePath.replace(/\.md$/, '').replace(/\//g, '-');

    commands.push({
      name,
      frontmatter,
      template: body,
    });
  }

  return commands;
}

const STATE_DIR_NAME = '.opencode';
const STATE_FILE_NAME = 'pijul-manager.json';
const DEFAULT_TRUNK_CHANNEL = 'main';

function nowIso(): string {
  return new Date().toISOString();
}

export function slugify(value: string): string {
  return value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 64);
}

async function ensureDir(dirPath: string): Promise<void> {
  await fs.mkdir(dirPath, { recursive: true });
}

async function pathExists(targetPath: string): Promise<boolean> {
  try {
    await fs.access(targetPath);
    return true;
  } catch {
    return false;
  }
}

async function readState(root: string): Promise<ManagerState> {
  const statePath = path.join(root, STATE_DIR_NAME, STATE_FILE_NAME);
  if (!(await pathExists(statePath))) {
    return {
      root,
      trunkChannel: DEFAULT_TRUNK_CHANNEL,
      features: {},
    };
  }
  const raw = await fs.readFile(statePath, 'utf8');
  const parsed = JSON.parse(raw) as ManagerState;
  return {
    root,
    trunkChannel: parsed.trunkChannel ?? DEFAULT_TRUNK_CHANNEL,
    remote: parsed.remote,
    features: parsed.features ?? {},
  };
}

async function writeState(root: string, state: ManagerState): Promise<void> {
  const stateDir = path.join(root, STATE_DIR_NAME);
  await ensureDir(stateDir);
  const statePath = path.join(stateDir, STATE_FILE_NAME);
  await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
}

async function ensurePijulRepo(root: string): Promise<void> {
  const pijulDir = path.join(root, '.pijul');
  if (!existsSync(pijulDir)) {
    throw new Error(`No .pijul directory found in ${root}`);
  }
}

async function runCommand(command: string, args: string[], cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  const proc = Bun.spawn([command, ...args], {
    cwd,
    stdout: 'pipe',
    stderr: 'pipe',
  });
  const stdout = await new Response(proc.stdout).text();
  const stderr = await new Response(proc.stderr).text();
  const exitCode = await proc.exited;
  return { exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
}

async function getCurrentPijulChannel(root: string): Promise<string | undefined> {
  const result = await runCommand('pijul', ['channel'], root);
  if (result.exitCode !== 0 || !result.stdout) return undefined;
  const lines = result.stdout.split('\n');
  for (const line of lines) {
    const starMatch = line.match(/^\s*\*\s*([^\s()]+)/);
    if (starMatch) return starMatch[1];
    const currentMatch = line.match(/^\s*([^\s()]+)\s*\(current\)\s*$/i);
    if (currentMatch) return currentMatch[1];
  }
  return undefined;
}

async function pijulDiffIsClean(cwd: string): Promise<boolean> {
  const result = await runCommand('pijul', ['diff'], cwd);
  return result.exitCode === 0 && result.stdout.length === 0 && result.stderr.length === 0;
}

async function ensureDefaultRemote(repoPath: string, remote: string): Promise<void> {
  const configPath = path.join(repoPath, '.pijul', 'config');
  let config = '';
  if (await pathExists(configPath)) {
    config = await fs.readFile(configPath, 'utf8');
  }
  const entry = `default_remote = "${remote}"`;
  if (config.trim().length === 0) {
    await fs.writeFile(configPath, `${entry}\n`, 'utf8');
    return;
  }
  if (config.match(/^default_remote\s*=.*$/m)) {
    const updated = config.replace(/^default_remote\s*=.*$/m, entry);
    await fs.writeFile(configPath, updated, 'utf8');
    return;
  }
  const suffix = config.endsWith('\n') ? '' : '\n';
  await fs.writeFile(configPath, `${config}${suffix}${entry}\n`, 'utf8');
}

function formatFeatureState(feature: FeatureState): string {
  return `${feature.feature} (${feature.status}) - workspace=${feature.workspace}`;
}

export const AnarchyPlugin: Plugin = async () => {
  const commands = await loadCommands();
  const z = tool.schema;

  const anarchyTool = tool({
    description: 'Returns a short anarchy manifesto line.',
    args: {},
    async execute() {
      return 'No gods, no masters.';
    },
  });

  const pijulManagerTool = tool({
    description: 'Manage parallel feature work on Pijul repositories.',
    args: {
      action: z.enum(['init', 'spawn', 'sync', 'record', 'push', 'status', 'close']),
      feature: z.string().optional(),
      trunk: z.string().optional(),
      remote: z.string().optional(),
      workspaceRoot: z.string().optional(),
      agent: z.string().optional(),
      message: z.string().optional(),
    },
    async execute(args: PijulManagerArgs) {
      const action = args.action;
      if (!action) {
        return 'Missing action. Expected one of: init, spawn, sync, record, push, status, close.';
      }

      const root = process.cwd();
      await ensurePijulRepo(root);
      const statePath = path.join(root, STATE_DIR_NAME, STATE_FILE_NAME);
      const hasStateFile = await pathExists(statePath);
      const state = await readState(root);

      if (action === 'init') {
        const providedTrunk = args.trunk?.trim();
        let trunk = providedTrunk || (hasStateFile ? state.trunkChannel : undefined);
        if (!trunk) trunk = (await getCurrentPijulChannel(root)) || DEFAULT_TRUNK_CHANNEL;
        state.trunkChannel = trunk;
        if (args.remote) state.remote = args.remote.trim();
        await writeState(root, state);
        return `Initialized pijul manager. trunk=${state.trunkChannel}${state.remote ? ` remote=${state.remote}` : ''}`;
      }

      if (action === 'status') {
        const features = Object.values(state.features);
        if (features.length === 0) return 'No active feature workspaces.';
        return features.map(formatFeatureState).join('\n');
      }

      if (action === 'close') {
        const featureKey = args.feature ? slugify(args.feature) : '';
        if (!featureKey || !state.features[featureKey]) {
          return 'Unknown feature; provide a valid feature name.';
        }
        state.features[featureKey].status = 'pushed';
        await writeState(root, state);
        return `Closed feature ${featureKey}.`;
      }

      if (action === 'spawn') {
        if (!args.feature) return 'Missing feature name.';
        const featureKey = slugify(args.feature);
        if (!featureKey) return 'Invalid feature name.';
        const workspaceRoot = args.workspaceRoot
          ? path.resolve(args.workspaceRoot)
          : path.join(os.tmpdir(), 'opencode-pijul-workspaces');
        const workspace = path.join(workspaceRoot, featureKey);

        await ensureDir(workspaceRoot);

        if (await pathExists(workspace)) {
          await ensureDefaultRemote(workspace, root);
          state.features[featureKey] = {
            feature: featureKey,
            workspace,
            agent: args.agent,
            status: 'active',
            lastSyncedTrunk: state.features[featureKey]?.lastSyncedTrunk,
            lastPush: state.features[featureKey]?.lastPush,
          };
          await writeState(root, state);
          return `Workspace already exists at ${workspace}.`;
        }

        const cloneResult = await runCommand('pijul', ['clone', root, workspace, '--no-prompt'], root);
        if (cloneResult.exitCode !== 0) {
          return `Failed to clone repo: ${cloneResult.stderr || cloneResult.stdout}`;
        }
        await ensureDefaultRemote(workspace, root);

        state.features[featureKey] = {
          feature: featureKey,
          workspace,
          agent: args.agent,
          status: 'active',
        };
        await writeState(root, state);
        return `Spawned ${featureKey} in ${workspace} on trunk.`;
      }

      if (action === 'sync') {
        if (!args.feature) return 'Missing feature name.';
        const featureKey = slugify(args.feature);
        const feature = state.features[featureKey];
        if (!feature) return 'Unknown feature; spawn it first.';

        await ensureDefaultRemote(feature.workspace, root);

        const pullResult = await runCommand(
          'pijul',
          ['pull', '--from-channel', state.trunkChannel, '-a', '--no-prompt'],
          feature.workspace,
        );
        if (pullResult.exitCode !== 0) {
          return `Failed to pull trunk: ${pullResult.stderr || pullResult.stdout}`;
        }

        const clean = await pijulDiffIsClean(feature.workspace);
        feature.lastSyncedTrunk = nowIso();
        feature.status = clean ? 'ready' : 'blocked';
        await writeState(root, state);

        if (!clean) {
          return `Sync completed, but working copy has unresolved changes. Resolve conflicts, record changes, then retry.`;
        }

        return `Synced ${featureKey} with trunk (${state.trunkChannel}).`;
      }

      if (action === 'record') {
        if (!args.feature) return 'Missing feature name.';
        const featureKey = slugify(args.feature);
        const feature = state.features[featureKey];
        if (!feature) return 'Unknown feature; spawn it first.';

        await ensureDefaultRemote(feature.workspace, root);
        const message = args.message?.trim() || `WIP: ${featureKey}`;

        const addResult = await runCommand(
          'pijul',
          ['add', '--recursive', '--no-prompt', '.'],
          feature.workspace,
        );
        if (addResult.exitCode !== 0) {
          return `Failed to add files: ${addResult.stderr || addResult.stdout}`;
        }

        const recordResult = await runCommand(
          'pijul',
          ['record', '-a', '-m', message, '--no-prompt'],
          feature.workspace,
        );
        if (recordResult.exitCode !== 0) {
          return `Failed to record changes: ${recordResult.stderr || recordResult.stdout}`;
        }

        feature.status = 'ready';
        await writeState(root, state);
        return `Recorded changes for ${featureKey}.`;
      }

      if (action === 'push') {
        if (!args.feature) return 'Missing feature name.';
        const featureKey = slugify(args.feature);
        const feature = state.features[featureKey];
        if (!feature) return 'Unknown feature; spawn it first.';

        const featureClean = await pijulDiffIsClean(feature.workspace);
        if (!featureClean) {
          feature.status = 'blocked';
          await writeState(root, state);
          return 'Feature workspace has unrecorded changes; resolve and record before push.';
        }

        const trunkClean = await pijulDiffIsClean(root);
        if (!trunkClean) {
          return 'Trunk workspace has unrecorded changes; clean trunk before pushing.';
        }

        const pushResult = await runCommand(
          'pijul',
          ['push', '--to-channel', state.trunkChannel, '-a', '--no-prompt'],
          feature.workspace,
        );
        if (pushResult.exitCode !== 0) {
          return `Failed to push trunk: ${pushResult.stderr || pushResult.stdout}`;
        }

        feature.status = 'pushed';
        feature.lastPush = nowIso();
        await writeState(root, state);
        return `Pushed ${featureKey} to trunk (${state.trunkChannel}).`;
      }

      return `Unknown action: ${action}`;
    },
  });

  return {
    tool: {
      anarchy_manifest: anarchyTool,
      pijul_manager: pijulManagerTool,
    },
    async config(config) {
      config.command = config.command ?? {};

      for (const cmd of commands) {
        config.command[cmd.name] = {
          template: cmd.template,
          description: cmd.frontmatter.description,
          agent: cmd.frontmatter.agent,
          model: cmd.frontmatter.model,
          subtask: cmd.frontmatter.subtask,
        };
      }
    },
  };
};