import fs from 'fs';
import path from 'path';

export const AGENTS_FILENAME = 'AGENTS.md';
export const SOUL_FILENAME = 'SOUL.md';
export const IDENTITY_FILENAME = 'IDENTITY.md';
export const USER_FILENAME = 'USER.md';
export const BOOTSTRAP_FILENAME = 'BOOTSTRAP.md';
export const MEMORY_FILENAME = 'MEMORY.md';
export const CLAUDE_COMPAT_FILENAME = 'CLAUDE.md';
export const MEMORY_DIRNAME = 'memory';

export const INJECTED_WORKSPACE_FILES = [
  AGENTS_FILENAME,
  SOUL_FILENAME,
  IDENTITY_FILENAME,
  USER_FILENAME,
  BOOTSTRAP_FILENAME,
] as const;

export interface WorkspaceContextFile {
  name: string;
  path: string;
  rawChars: number;
  injectedChars: number;
  truncated: boolean;
  missing: boolean;
}

export interface WorkspaceContextBuildResult {
  text: string;
  files: WorkspaceContextFile[];
  totalInjectedChars: number;
}

function readTextFile(filePath: string): string {
  return fs.readFileSync(filePath, 'utf-8').replace(/\r\n/g, '\n').trim();
}

function ensureFile(filePath: string, content: string): void {
  if (!fs.existsSync(filePath)) {
    fs.writeFileSync(filePath, content);
  }
}

function ensureRelativeSymlink(linkPath: string, targetBasename: string): void {
  if (fs.existsSync(linkPath)) {
    const stat = fs.lstatSync(linkPath);
    if (stat.isSymbolicLink() && fs.readlinkSync(linkPath) === targetBasename) {
      return;
    }
    fs.rmSync(linkPath, { force: true });
  }
  fs.symlinkSync(targetBasename, linkPath);
}

function buildInheritedReferenceContent(fileName: string): string {
  return [
    `# ${fileName}`,
    '',
    'This group inherits its default profile from the tenant main group.',
    `Read /workspace/main-profile/${fileName} before using or editing this file.`,
    'Add only this group\'s overrides or clarifications below.',
    '',
  ].join('\n');
}

function replaceSymlinkWithFile(filePath: string, content: string): void {
  try {
    const stat = fs.lstatSync(filePath);
    if (!stat.isSymbolicLink()) return;
  } catch {
    return;
  }

  fs.rmSync(filePath, { force: true });
  fs.writeFileSync(filePath, content);
}

function migrateLegacyClaudeIntoMemory(groupDir: string): void {
  const memoryPath = path.join(groupDir, MEMORY_FILENAME);
  const claudePath = path.join(groupDir, CLAUDE_COMPAT_FILENAME);

  if (!fs.existsSync(claudePath)) {
    return;
  }

  try {
    const stat = fs.lstatSync(claudePath);
    if (stat.isSymbolicLink()) {
      return;
    }
  } catch {
    return;
  }

  const legacyContent = readTextFile(claudePath);
  const existingMemory = fs.existsSync(memoryPath) ? readTextFile(memoryPath) : '';
  if (!existingMemory) {
    fs.writeFileSync(memoryPath, legacyContent ? `${legacyContent}\n` : '');
  } else if (legacyContent && legacyContent !== existingMemory) {
    const merged = `${existingMemory}\n\n## Migrated from legacy CLAUDE.md\n\n${legacyContent}\n`;
    fs.writeFileSync(memoryPath, merged);
  }

  const backupPath = `${claudePath}.legacy`;
  if (!fs.existsSync(backupPath)) {
    fs.renameSync(claudePath, backupPath);
  } else {
    fs.rmSync(claudePath, { force: true });
  }
}

export function ensureWorkspaceContract(
  groupDir: string,
  options: {
    mainGroupDir?: string;
    createDefaults?: boolean;
    defaults?: Partial<Record<
      typeof AGENTS_FILENAME
      | typeof SOUL_FILENAME
      | typeof IDENTITY_FILENAME
      | typeof USER_FILENAME
      | typeof MEMORY_FILENAME
      | typeof BOOTSTRAP_FILENAME,
      string
    >>;
  } = {},
): void {
  fs.mkdirSync(groupDir, { recursive: true });
  fs.mkdirSync(path.join(groupDir, MEMORY_DIRNAME), { recursive: true });

  const memoryPath = path.join(groupDir, MEMORY_FILENAME);
  if (options.createDefaults) {
    ensureFile(memoryPath, options.defaults?.[MEMORY_FILENAME] ?? '# Memory\n\n');
  } else if (!fs.existsSync(memoryPath)) {
    ensureFile(memoryPath, '# Memory\n\n');
  }

  migrateLegacyClaudeIntoMemory(groupDir);
  ensureRelativeSymlink(path.join(groupDir, CLAUDE_COMPAT_FILENAME), MEMORY_FILENAME);

  const inheritedFiles = [
    AGENTS_FILENAME,
    SOUL_FILENAME,
    IDENTITY_FILENAME,
    USER_FILENAME,
  ] as const;

  for (const fileName of inheritedFiles) {
    const filePath = path.join(groupDir, fileName);
    if (options.mainGroupDir) {
      const sourcePath = path.join(options.mainGroupDir, fileName);
      const inheritedContent = buildInheritedReferenceContent(fileName);

      if (fs.existsSync(sourcePath)) {
        if (!fs.existsSync(filePath)) {
          fs.writeFileSync(filePath, inheritedContent);
          continue;
        }

        try {
          const stat = fs.lstatSync(filePath);
          if (stat.isSymbolicLink() && fs.readlinkSync(filePath) === sourcePath) {
            replaceSymlinkWithFile(filePath, inheritedContent);
            continue;
          }
        } catch {
          // Ignore and preserve the existing file.
        }
      }
    }

    if (options.createDefaults) {
      ensureFile(filePath, options.defaults?.[fileName] ?? `# ${fileName}\n\n`);
    }
  }

  if (options.createDefaults && options.defaults?.[BOOTSTRAP_FILENAME] !== undefined) {
    ensureFile(path.join(groupDir, BOOTSTRAP_FILENAME), options.defaults[BOOTSTRAP_FILENAME]!);
  }
}

export function buildWorkspaceContextAppend(
  groupDir: string,
  options: {
    maxFileChars?: number;
    maxTotalChars?: number;
  } = {},
): WorkspaceContextBuildResult {
  const maxFileChars = options.maxFileChars ?? 8_000;
  const maxTotalChars = options.maxTotalChars ?? 24_000;
  const files: WorkspaceContextFile[] = [];
  const blocks: string[] = [];
  let totalInjectedChars = 0;

  for (const fileName of INJECTED_WORKSPACE_FILES) {
    const filePath = path.join(groupDir, fileName);
    if (!fs.existsSync(filePath)) {
      files.push({
        name: fileName,
        path: filePath,
        rawChars: 0,
        injectedChars: 0,
        truncated: false,
        missing: true,
      });
      continue;
    }

    const raw = readTextFile(filePath);
    const rawChars = raw.length;
    const perFile = raw.slice(0, maxFileChars);
    const remaining = Math.max(0, maxTotalChars - totalInjectedChars);
    const injected = perFile.slice(0, remaining);
    const injectedChars = injected.length;
    const truncated = injectedChars < rawChars;

    files.push({
      name: fileName,
      path: filePath,
      rawChars,
      injectedChars,
      truncated,
      missing: false,
    });

    if (!injected) {
      continue;
    }

    blocks.push(
      `<file name="${fileName}"${truncated ? ' truncated="true"' : ''}>\n${injected}\n</file>`,
    );
    totalInjectedChars += injectedChars;
    if (totalInjectedChars >= maxTotalChars) {
      break;
    }
  }

  if (blocks.length === 0) {
    return { text: '', files, totalInjectedChars };
  }

  const text = [
    '## Workspace Contract',
    '- Treat AGENTS.md as a tenant-writable instructions/config file for how the agent should operate in this group.',
    '- Platform-enforced context may also be appended separately at runtime and is not replaced by AGENTS.md edits.',
    '- Treat SOUL.md and IDENTITY.md as writable profile files the tenant can shape.',
    '- Treat USER.md as a writable tenant profile file for facts and preferences about the user.',
    '- Treat MEMORY.md as the canonical writable memory file.',
    '- If you need to remember something durable, update the most relevant writable file: AGENTS.md for durable operating instructions, USER.md for user facts/preferences, SOUL.md or IDENTITY.md for assistant profile shaping, and MEMORY.md or files under memory/ for general durable memory that does not belong in a profile/config file.',
    '- When you confirm a memory/profile/onboarding update to the user, describe the outcome plainly and do not mention workspace file names or paths unless the user explicitly asks where it was stored.',
    '- Do not use AGENTS.md as a catch-all memory log; keep general memory in MEMORY.md or memory/.',
    '- For resource questions, report sandbox-visible values only. Use /proc/meminfo or free -h for memory/swap and df -h /workspace/group for disk.',
    '- Do not use nproc, /proc/cpuinfo, or old remembered values to claim CPU core counts or system-wide resources.',
    '- If current resource checks are blocked in this turn, say so clearly and do not reuse stale numbers from earlier turns.',
    '- If a user asks to build, create, scaffold, run, deploy, host, or expose an app, site, frontend, backend, or API, treat that as a hosted-app request by default.',
    '- Hosted-app work is not complete until source is under /workspace/apps/<app>/, app.yml exists, app-apply has been sent, and the real public URL has been verified.',
    '- Do not stop at local source files or tell the user to run the app themselves with npm start, node, Docker Compose, or similar handoff steps unless they explicitly asked for local-only work.',
    '',
    '## Project Context',
    '<workspace_context>',
    ...blocks,
    '</workspace_context>',
    '',
  ].join('\n');

  return { text, files, totalInjectedChars };
}
