import type { Plugin, PluginInput } 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;
  agentId?: string;
  sessionId?: string;
  parentFeature?: string;
  parentAgentId?: string;
  parentSessionId?: string;
  childFeatures?: string[];
  depth?: number;
  status: FeatureStatus;
  lastSyncedTrunk?: string;
  lastRecorded?: 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;
  description?: string;
  prompt?: string;
  spawns?: SpawnRequest[];
  parentFeature?: string;
  parentAgentId?: string;
  parentSessionId?: string;
  trunk?: string;
  remote?: string;
  workspaceRoot?: string;
  agent?: string;
  agentId?: string;
  sessionId?: string;
  message?: string;
}

interface SpawnRequest {
  feature: string;
  description: string;
  prompt: string;
  agent: string;
  parentFeature?: string;
  parentAgentId?: string;
  parentSessionId?: string;
  workspaceRoot?: 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';
const DEFAULT_FEATURE_DEPTH = 1;
const MAX_FEATURE_DEPTH = 5;

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

function isoAfterOrEqual(a?: string, b?: string): boolean {
  if (!a || !b) return false;
  return Date.parse(a) >= Date.parse(b);
}

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;
  const features: Record<string, FeatureState> = {};
  for (const [key, feature] of Object.entries(parsed.features ?? {})) {
    features[key] = {
      ...feature,
      agentId: feature.agentId ?? feature.agent,
      depth: feature.depth ?? DEFAULT_FEATURE_DEPTH,
      childFeatures: feature.childFeatures ?? [],
    };
  }
  return {
    root,
    trunkChannel: parsed.trunkChannel ?? DEFAULT_TRUNK_CHANNEL,
    remote: parsed.remote,
    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 runPijulCommand(args: string[], cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  const proc = Bun.spawn(['pijul', ...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 pijulChannel(root: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['channel'], root);
}

async function pijulDiff(cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['diff'], cwd);
}

async function pijulCloneRepo(source: string, destination: string, cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['clone', source, destination, '--no-prompt'], cwd);
}

async function pijulPullFromChannel(cwd: string, channel: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['pull', '--from-channel', channel, '-a', '--no-prompt'], cwd);
}

async function pijulAddAll(cwd: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['add', '--recursive', '--no-prompt', '.'], cwd);
}

async function pijulRecordAll(cwd: string, message: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['record', '-a', '-m', message, '--no-prompt'], cwd);
}

async function pijulPushToChannel(cwd: string, channel: string): Promise<{ exitCode: number; stdout: string; stderr: string }> {
  return runPijulCommand(['push', '--to-channel', channel, '-a', '--no-prompt'], cwd);
}

async function getCurrentPijulChannel(root: string): Promise<string | undefined> {
  const result = await pijulChannel(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 pijulDiff(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 {
  const depth = feature.depth ?? DEFAULT_FEATURE_DEPTH;
  const parent = feature.parentFeature ?? 'user';
  return `${feature.feature} (${feature.status}) depth=${depth} parent=${parent} workspace=${feature.workspace}`;
}

function resolveSpawnRequests(args: PijulManagerArgs): SpawnRequest[] {
  if (args.spawns && args.spawns.length > 0) return args.spawns;
  if (!args.feature) return [];
  if (!args.description || !args.prompt || !args.agent) return [];
  return [
    {
      feature: args.feature,
      description: args.description,
      prompt: args.prompt,
      agent: args.agent,
      parentFeature: args.parentFeature,
      parentAgentId: args.parentAgentId,
      parentSessionId: args.parentSessionId,
      workspaceRoot: args.workspaceRoot,
    },
  ];
}

function argsContainPijul(value: unknown): boolean {
  if (!value) return false;
  if (typeof value === 'string') return /(^|\s)pijul(\s|$)/.test(value);
  if (Array.isArray(value)) return value.some((entry) => argsContainPijul(entry));
  if (typeof value === 'object') {
    return Object.values(value as Record<string, unknown>).some((entry) => argsContainPijul(entry));
  }
  return false;
}

async function createSubtaskSession(input: {
  client: PluginInput['client'];
  directory: string;
  parentSessionId: string;
  agent: string;
  title: string;
  prompt: string;
}): Promise<string> {
  const createResponse = await input.client.session.create({
    query: { directory: input.directory },
    body: { parentID: input.parentSessionId, title: input.title },
  });
  const createError = (createResponse as { error?: { message?: string } }).error;
  if (!createResponse || createError) {
    const error = createError;
    throw new Error(error?.message ?? 'Failed to create subtask session.');
  }
  const session = (createResponse as { data?: { id?: string } }).data;
  if (!session?.id) throw new Error('Failed to create subtask session.');

  const promptResponse = await input.client.session.prompt({
    path: { id: session.id },
    query: { directory: input.directory },
    body: {
      agent: input.agent,
      parts: [
        {
          type: 'text',
          text: input.prompt,
        },
      ],
    },
  });
  const promptError = (promptResponse as { error?: { message?: string } }).error;
  if (!promptResponse || promptError) {
    const error = promptError;
    throw new Error(error?.message ?? 'Failed to start subtask session.');
  }

  return session.id;
}

export const AnarchyPlugin: Plugin = async (input) => {
  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(),
      description: z.string().optional(),
      prompt: z.string().optional(),
      spawns: z
        .array(
          z.object({
            feature: z.string(),
            description: z.string(),
            prompt: z.string(),
            agent: z.string(),
            parentFeature: z.string().optional(),
            parentAgentId: z.string().optional(),
            parentSessionId: z.string().optional(),
            workspaceRoot: z.string().optional(),
          }),
        )
        .optional(),
      parentFeature: z.string().optional(),
      parentAgentId: z.string().optional(),
      parentSessionId: z.string().optional(),
      trunk: z.string().optional(),
      remote: z.string().optional(),
      workspaceRoot: z.string().optional(),
      agent: z.string().optional(),
      agentId: z.string().optional(),
      sessionId: z.string().optional(),
      message: z.string().optional(),
    },
    async execute(args: PijulManagerArgs, context) {
      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') {
        const spawnRequests = resolveSpawnRequests(args);
        if (spawnRequests.length === 0) {
          return 'Missing spawn details. Provide feature + description + prompt + agent, or use spawns[].';
        }

        const results: Array<{ feature: string; workspace: string; sessionId?: string; status: string }> = [];
        const subtaskJobs: Array<{
          featureKey: string;
          workspace: string;
          agent: string;
          title: string;
          prompt: string;
          parentSessionId: string;
        }> = [];

        for (const request of spawnRequests) {
          const featureKey = slugify(request.feature);
          if (!featureKey) return 'Invalid feature name.';
          if (!request.agent || !request.description || !request.prompt) {
            return `Missing subtask details for ${request.feature}; include agent, description, and prompt.`;
          }
          const parentKey = request.parentFeature ? slugify(request.parentFeature) : undefined;
          let parentFeature: FeatureState | undefined;
          let depth = DEFAULT_FEATURE_DEPTH;
          let parentAgentId = request.parentAgentId ?? args.parentAgentId ?? context.agent;
          let parentSessionId = request.parentSessionId ?? args.parentSessionId ?? context.sessionID;
          if (parentKey) {
            parentFeature = state.features[parentKey];
            if (!parentFeature) return 'Unknown parent feature; spawn the parent first.';
            depth = (parentFeature.depth ?? DEFAULT_FEATURE_DEPTH) + 1;
            if (depth > MAX_FEATURE_DEPTH) {
              return `Max feature depth ${MAX_FEATURE_DEPTH} exceeded.`;
            }
            parentAgentId = parentAgentId ?? parentFeature.agentId ?? parentFeature.agent ?? context.agent;
            parentSessionId = parentSessionId ?? parentFeature.sessionId ?? context.sessionID;
          }

          const workspaceRoot = request.workspaceRoot
            ? path.resolve(request.workspaceRoot)
            : args.workspaceRoot
              ? path.resolve(args.workspaceRoot)
              : path.join(os.tmpdir(), 'opencode-pijul-workspaces');
          const workspace = path.join(workspaceRoot, featureKey);
          await ensureDir(workspaceRoot);

          const existing = state.features[featureKey];
          const alreadyExists = await pathExists(workspace);
          if (!alreadyExists) {
            const cloneResult = await pijulCloneRepo(root, workspace, root);
            if (cloneResult.exitCode !== 0) {
              return `Failed to clone repo: ${cloneResult.stderr || cloneResult.stdout}`;
            }
          }
          await ensureDefaultRemote(workspace, root);

          const sessionId = existing?.sessionId;
          state.features[featureKey] = {
            feature: featureKey,
            workspace,
            agent: request.agent,
            agentId: existing?.agentId ?? request.agent,
            sessionId,
            parentFeature: parentKey,
            parentAgentId,
            parentSessionId,
            childFeatures: existing?.childFeatures ?? [],
            depth: existing?.depth ?? depth,
            status: 'active',
            lastSyncedTrunk: existing?.lastSyncedTrunk,
            lastRecorded: existing?.lastRecorded,
            lastPush: existing?.lastPush,
          };
          if (parentKey && parentFeature) {
            const children = new Set(parentFeature.childFeatures ?? []);
            children.add(featureKey);
            parentFeature.childFeatures = Array.from(children);
          }

          results.push({
            feature: featureKey,
            workspace,
            sessionId,
            status: alreadyExists ? 'existing' : 'cloned',
          });

          if (!sessionId) {
            subtaskJobs.push({
              featureKey,
              workspace,
              agent: request.agent,
              title: request.description,
              prompt: `${request.prompt.trim()}\n\nWorkspace: ${workspace}\nFeature: ${featureKey}`,
              parentSessionId: parentSessionId ?? context.sessionID,
            });
          }
        }

        const subtaskResults = await Promise.allSettled(
          subtaskJobs.map((job) =>
            createSubtaskSession({
              client: input.client,
              directory: input.directory,
              parentSessionId: job.parentSessionId,
              agent: job.agent,
              title: job.title,
              prompt: job.prompt,
            }).then((sessionId) => ({ featureKey: job.featureKey, sessionId })),
          ),
        );

        const subtaskErrors: string[] = [];
        for (const result of subtaskResults) {
          if (result.status === 'fulfilled') {
            const featureState = state.features[result.value.featureKey];
            if (featureState) featureState.sessionId = result.value.sessionId;
            const entry = results.find((item) => item.feature === result.value.featureKey);
            if (entry) entry.sessionId = result.value.sessionId;
          } else {
            subtaskErrors.push(result.reason instanceof Error ? result.reason.message : String(result.reason));
          }
        }

        await writeState(root, state);

        const lines = results.map((item) => {
          const session = item.sessionId ? ` session=${item.sessionId}` : ' session=pending';
          return `${item.feature} ${item.status} workspace=${item.workspace}${session}`;
        });
        if (subtaskErrors.length > 0) {
          lines.push(`Subtask errors: ${subtaskErrors.join(' | ')}`);
        }
        return lines.join('\n');
      }

      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 pijulPullFromChannel(feature.workspace, state.trunkChannel);
        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 pijulAddAll(feature.workspace);
        if (addResult.exitCode !== 0) {
          return `Failed to add files: ${addResult.stderr || addResult.stdout}`;
        }

        const recordResult = await pijulRecordAll(feature.workspace, message);
        if (recordResult.exitCode !== 0) {
          return `Failed to record changes: ${recordResult.stderr || recordResult.stdout}`;
        }

        feature.status = 'ready';
        feature.lastRecorded = nowIso();
        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.';

        if (!feature.lastSyncedTrunk) {
          return 'Sync with trunk required before push; run sync.';
        }
        if (!feature.lastRecorded) {
          return 'Record required before push; run record.';
        }
        if (!isoAfterOrEqual(feature.lastRecorded, feature.lastSyncedTrunk)) {
          return 'Record must happen after sync; re-sync, resolve conflicts, and record again.';
        }

        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 pijulPushToChannel(feature.workspace, state.trunkChannel);
        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 'tool.execute.before'(input, output) {
      if (input.tool !== 'shell' && input.tool !== 'bash') return;
      if (!argsContainPijul(output.args)) return;
      throw new Error('Direct pijul usage is disabled. Use pijul_manager tool actions instead.');
    },
    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,
        };
      }
    },
  };
};