const fs = require('fs');
const path = require('path');

const green = (msg) => `\x1b[32m${msg}\x1b[0m`;
const red = (msg) => `\x1b[31m${msg}\x1b[0m`;
const yellow = (msg) => `\x1b[33m${msg}\x1b[0m`;
const bold = (msg) => `\x1b[1m${msg}\x1b[0m`;

const BUILTIN_SEGMENTS = ['commerce', 'locale', 'currency', 'url'];

function copyDirRecursive(src, dest) {
  fs.mkdirSync(dest, { recursive: true });
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    if (entry.isDirectory()) {
      copyDirRecursive(srcPath, destPath);
    } else {
      fs.copyFileSync(srcPath, destPath);
    }
  }
}

function countEntries(dir) {
  let count = 0;
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    count++;
    if (entry.isDirectory()) {
      count += countEntries(path.join(dir, entry.name));
    }
  }
  return count;
}

/**
 * Walks down from [commerce]/ through nested dynamic segments,
 * collecting segment names and the directories at each level.
 *
 * Handles intermediate content (e.g. layout.tsx at [locale]/ level)
 * by continuing to descend as long as a single-bracket dynamic child exists.
 *
 * Example:
 *   [commerce]/[locale]/[currency]/[url]/...
 *   → segments: ['locale', 'currency', 'url']
 *   → levels: [{ dir: '[locale]/', segmentChild: '[currency]' }, ...]
 *   → contentDir: the deepest level with route content
 */
function detectSegments(commerceDir) {
  const segments = [];
  const levels = []; // { dir, segmentChildName } for each intermediate level
  let currentDir = commerceDir;

  while (true) {
    const entries = fs.readdirSync(currentDir, { withFileTypes: true });

    // Find single-bracket dynamic segment dirs (not catch-all [...])
    const dynamicDirs = entries.filter(
      (e) =>
        e.isDirectory() &&
        e.name.startsWith('[') &&
        e.name.endsWith(']') &&
        !e.name.startsWith('[...')
    );

    if (dynamicDirs.length === 1) {
      const segName = dynamicDirs[0].name.slice(1, -1);
      segments.push(segName);
      levels.push({ dir: currentDir, segmentChildName: dynamicDirs[0].name });
      currentDir = path.join(currentDir, dynamicDirs[0].name);
    } else {
      // No more single dynamic segment children — this is the deepest content level
      break;
    }
  }

  return { segments, levels, contentDir: currentDir };
}

/**
 * Copies non-segment content from each intermediate level into dest,
 * then copies the deepest content level on top (deeper wins on conflict).
 */
function mergeContentIntoPz(levels, contentDir, pzDir) {
  // First: copy non-segment content from each intermediate level (shallowest first)
  for (const { dir, segmentChildName } of levels) {
    const entries = fs.readdirSync(dir, { withFileTypes: true });
    for (const entry of entries) {
      // Skip the segment child directory itself
      if (entry.name === segmentChildName) continue;

      const srcPath = path.join(dir, entry.name);
      const destPath = path.join(pzDir, entry.name);
      if (entry.isDirectory()) {
        copyDirRecursive(srcPath, destPath);
      } else {
        fs.mkdirSync(pzDir, { recursive: true });
        fs.copyFileSync(srcPath, destPath);
      }
    }
  }

  // Last: copy deepest content level (overwrites conflicts from shallower levels)
  copyDirRecursive(contentDir, pzDir);
}

/**
 * Reads middleware.ts and extracts the expression used to build
 * the URL rewrite for each custom segment.
 *
 * Looks for patterns like:
 *   url.pathname = '/' + <expression> + url.pathname
 *   url.pathname = `/${<expression>}${url.pathname}`
 *
 * Then maps that expression into a resolve function.
 */
function analyzeMiddleware(workingDir, customSegments) {
  const hints = {};

  const middlewarePath = path.join(workingDir, 'src', 'middleware.ts');
  if (!fs.existsSync(middlewarePath)) return hints;

  const content = fs.readFileSync(middlewarePath, 'utf-8');

  // Extract the rewrite expression from url.pathname assignment
  // Pattern 1: url.pathname = '/' + <expr> + url.pathname
  const concatMatch = content.match(
    /url\.pathname\s*=\s*['"`]\/['"`]\s*\+\s*(.+?)\s*\+\s*url\.pathname/
  );
  // Pattern 2: url.pathname = `/${<expr>}${url.pathname}`
  const templateMatch = content.match(
    /url\.pathname\s*=\s*`\/\$\{(.+?)\}\$\{url\.pathname\}`/
  );

  const rewriteExpression = concatMatch?.[1]?.trim() || templateMatch?.[1]?.trim();

  for (const seg of customSegments) {
    // If we found a rewrite expression in middleware, use it directly
    if (rewriteExpression) {
      hints[seg] = {
        source: 'middleware-rewrite',
        code: `({ req }) => ${rewriteExpression}`
      };
      continue;
    }

    // Check for cookie pattern: req.cookies.get('pz-{seg}') or similar
    const cookieRegex = new RegExp(
      `cookies\\.get\\(['"\`](?:pz-)?${seg}['"\`]\\)`,
      'i'
    );
    if (cookieRegex.test(content)) {
      hints[seg] = {
        source: 'cookie',
        code: `({ req }) => req.cookies.get('pz-${seg}')?.value ?? ''`
      };
      continue;
    }

    // Check for header pattern: req.headers.get('x-{seg}')
    const headerRegex = new RegExp(
      `headers\\.get\\(['"\`](?:x-)?${seg}['"\`]\\)`,
      'i'
    );
    if (headerRegex.test(content)) {
      hints[seg] = {
        source: 'header',
        code: `({ req }) => req.headers.get('x-${seg}') ?? ''`
      };
      continue;
    }

    // No hint found
    hints[seg] = {
      source: 'unknown',
      code: null
    };
  }

  return hints;
}

/**
 * Removes the rewrite block from middleware.ts since resolve now handles it.
 * Strips: url clone, url.pathname assignment, NextResponse.rewrite with pz-override-response.
 * Leaves the middleware function body empty (or with remaining custom logic).
 */
function cleanupMiddleware(workingDir) {
  const middlewarePath = path.join(workingDir, 'src', 'middleware.ts');
  if (!fs.existsSync(middlewarePath)) return false;

  let content = fs.readFileSync(middlewarePath, 'utf-8');

  if (!content.includes('pz-override-response')) return false;

  // Remove: const url = req.nextUrl.clone();
  content = content.replace(
    /\n?\s*const url = req\.nextUrl\.clone\(\);\s*\n?/,
    '\n'
  );

  // Remove: url.pathname = ... + url.pathname;
  content = content.replace(
    /\s*url\.pathname\s*=\s*.+?url\.pathname.*;\s*\n?/,
    ''
  );

  // Replace NextResponse.rewrite(url, { headers: { 'pz-override-response': ... } })
  // with empty return (or no return)
  content = content.replace(
    /\s*return\s+NextResponse\.rewrite\(url,\s*\{[\s\S]*?'pz-override-response'[\s\S]*?\}\s*\);\s*/,
    '\n'
  );

  // Clean up empty lines left behind
  content = content.replace(/\n{3,}/g, '\n\n');

  fs.writeFileSync(middlewarePath, content);
  return true;
}

/**
 * Replaces old segment path references in project files (tsconfig, imports, etc.)
 * e.g. ./app/[commerce]/[locale]/[currency]/[url]/ → ./app/[pz]/
 */
function updatePathReferences(workingDir, segments) {
  // Build old path patterns from detected segments in different formats
  // Bracket format: [commerce]/[locale]/[currency]/[url]
  const bracketPath = ['[commerce]', ...segments.map((s) => `[${s}]`)].join(
    '/'
  );
  // Colon format (Next.js rewrites): :commerce/:locale/:currency/:url
  const colonPath = [':commerce', ...segments.map((s) => `:${s}`)].join('/');

  const replacements = [
    { from: bracketPath, to: '[pz]' },
    { from: colonPath, to: ':pz' }
  ];

  const extensions = ['.json', '.js', '.mjs', '.ts', '.tsx'];
  let updatedFiles = [];

  function walkAndReplace(dir) {
    let entries;
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch {
      return;
    }

    for (const entry of entries) {
      if (entry.name === 'node_modules' || entry.name === '.next') continue;
      // Skip the app directory itself (already handled by directory migration)
      if (entry.name === 'app' && dir === path.join(workingDir, 'src')) continue;

      const fullPath = path.join(dir, entry.name);

      if (entry.isDirectory()) {
        walkAndReplace(fullPath);
      } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
        let content;
        try {
          content = fs.readFileSync(fullPath, 'utf-8');
        } catch {
          continue;
        }

        let changed = false;
        for (const { from, to } of replacements) {
          if (content.includes(from)) {
            content = content.split(from).join(to);
            changed = true;
          }
        }

        if (changed) {
          fs.writeFileSync(fullPath, content);
          updatedFiles.push(path.relative(workingDir, fullPath));
        }
      }
    }
  }

  walkAndReplace(workingDir);
  return updatedFiles;
}

function updateParamsUsage(workingDir) {
  const extensions = ['.ts', '.tsx'];
  const updatedFiles = [];

  // Patterns to replace inline
  const replacements = [
    // decodeURIComponent(params.url) → parsePzParams(params, settings).url
    {
      pattern: /decodeURIComponent\(params\.url\)/g,
      replacement: 'parsePzParams(params, settings).url'
    },
    // params.locale → parsePzParams(params, settings).locale
    {
      pattern: /params\.locale/g,
      replacement: 'parsePzParams(params, settings).locale'
    },
    // params.currency → parsePzParams(params, settings).currency
    {
      pattern: /params\.currency/g,
      replacement: 'parsePzParams(params, settings).currency'
    },
    // params.url (standalone, not already wrapped) → parsePzParams(params, settings).url
    {
      pattern: /params\.url/g,
      replacement: 'parsePzParams(params, settings).url'
    }
  ];

  function walk(dir) {
    let entries;
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch {
      return;
    }

    for (const entry of entries) {
      if (entry.name === 'node_modules' || entry.name === '.next') continue;
      const fullPath = path.join(dir, entry.name);

      if (entry.isDirectory()) {
        walk(fullPath);
      } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
        let content;
        try {
          content = fs.readFileSync(fullPath, 'utf-8');
        } catch {
          continue;
        }

        // Check if file has any params.locale/currency/url usage
        if (!/params\.(locale|currency|url)/.test(content)) continue;

        let updated = content;
        for (const { pattern, replacement } of replacements) {
          updated = updated.replace(pattern, replacement);
        }

        if (updated !== content) {
          // Add import if not already present
          if (!updated.includes('parsePzParams')) {
            // This shouldn't happen since we just added it, but safety check
          }
          if (
            !updated.includes("from '@akinon/next/utils'") &&
            !updated.includes("from '@akinon/next/utils/pz-segments'")
          ) {
            // Add import at the top after last import
            const lastImportIdx = updated.lastIndexOf('\nimport ');
            if (lastImportIdx !== -1) {
              const lineEnd = updated.indexOf('\n', lastImportIdx + 1);
              updated =
                updated.slice(0, lineEnd + 1) +
                "import { parsePzParams } from '@akinon/next/utils';\n" +
                updated.slice(lineEnd + 1);
            } else {
              updated =
                "import { parsePzParams } from '@akinon/next/utils';\n" +
                updated;
            }
          } else {
            // File already imports from @akinon/next/utils, add parsePzParams to existing import
            const importRegex =
              /import\s*\{([^}]*)\}\s*from\s*['"]@akinon\/next\/utils['"]/;
            const match = updated.match(importRegex);
            if (match && !match[1].includes('parsePzParams')) {
              updated = updated.replace(
                importRegex,
                `import {${match[1]}, parsePzParams } from '@akinon/next/utils'`
              );
            }
          }

          // Ensure settings import exists (check for any settings import: 'settings', '@theme/settings', './settings', etc.)
          const hasSettingsImport = /import\s+settings\s+from\s+['"]/.test(updated);
          if (!hasSettingsImport) {
            const lastImportIdx = updated.lastIndexOf('\nimport ');
            if (lastImportIdx !== -1) {
              const lineEnd = updated.indexOf('\n', lastImportIdx + 1);
              updated =
                updated.slice(0, lineEnd + 1) +
                "import settings from 'settings';\n" +
                updated.slice(lineEnd + 1);
            }
          }

          fs.writeFileSync(fullPath, updated);
          updatedFiles.push(path.relative(workingDir, fullPath));
        }
      }
    }
  }

  walk(path.join(workingDir, 'src'));
  return updatedFiles;
}

function generatePzSegmentsConfig(segments, resolveHints) {
  const lines = segments.map((name) => {
    const hint = resolveHints[name];
    if (hint && hint.code) {
      return `    { name: '${name}', resolve: ${hint.code} }`;
    }
    if (hint && hint.source === 'unknown') {
      return `    { name: '${name}', resolve: ({ req }) => '' /* TODO: return ${name} value */ }`;
    }
    return `    { name: '${name}' }`;
  });
  return `pzSegments: {\n    segments: [\n${lines.join(',\n')}\n    ]\n  }`;
}

const transform = () => {
  const workingDir = path.resolve(process.cwd());
  const appDir = path.join(workingDir, 'src', 'app');
  const settingsPath = path.join(workingDir, 'src', 'settings.js');

  if (!fs.existsSync(appDir)) {
    console.error(red('Error: src/app/ directory not found.'));
    process.exit(1);
  }

  const pzDir = path.join(appDir, '[pz]');
  if (fs.existsSync(pzDir)) {
    console.log(
      yellow(
        '[pz] directory already exists. Migration may have already been done.'
      )
    );
    process.exit(0);
  }

  const commerceDir = path.join(appDir, '[commerce]');
  if (!fs.existsSync(commerceDir)) {
    console.error(red('Error: [commerce] directory not found in src/app/.'));
    process.exit(1);
  }

  // Detect all segments by walking down the directory tree
  const { segments, levels, contentDir } = detectSegments(commerceDir);

  const customSegments = segments.filter(
    (s) => !BUILTIN_SEGMENTS.includes(s)
  );
  const segmentPath = ['[commerce]', ...segments.map((s) => `[${s}]`)].join(
    '/'
  );

  // Count entries across all levels that will be merged
  let totalEntries = countEntries(contentDir);
  for (const { dir, segmentChildName } of levels) {
    for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
      if (entry.name === segmentChildName) continue;
      totalEntries++;
      if (entry.isDirectory()) {
        totalEntries += countEntries(path.join(dir, entry.name));
      }
    }
  }

  console.log(bold('\nPZ Segment Migration'));
  console.log('====================\n');
  console.log(`Detected: ${yellow(segmentPath + '/')}`);
  console.log(`Segments: ${segments.join(', ')}`);
  if (customSegments.length > 0) {
    console.log(
      `Custom:   ${yellow(customSegments.join(', '))} (will be added to pzSegments)`
    );
  }
  if (levels.some(({ dir, segmentChildName }) =>
    fs.readdirSync(dir).some((f) => f !== segmentChildName)
  )) {
    console.log(`Note:     Intermediate levels have content, will be merged`);
  }
  console.log(`Target:   ${yellow('[pz]/')}`);
  console.log(`Entries:  ${totalEntries}\n`);

  // Merge all levels into [pz]/
  console.log('Merging content into [pz]/...');
  mergeContentIntoPz(levels, contentDir, pzDir);

  // Remove legacy [commerce]/ tree
  console.log('Removing legacy directories...');
  fs.rmSync(commerceDir, { recursive: true, force: true });

  // Update settings.js
  if (fs.existsSync(settingsPath)) {
    let content = fs.readFileSync(settingsPath, 'utf-8');

    // Add usePzSegment: true
    if (!content.includes('usePzSegment')) {
      content = content.replace(/(\n};\s*$)/, ',\n  usePzSegment: true$1');

      if (!content.includes('usePzSegment')) {
        content = content.replace(/(};\s*$)/, '  usePzSegment: true,\n$1');
      }
    }

    // Add pzSegments config if custom segments were found
    if (customSegments.length > 0 && !content.includes('pzSegments')) {
      const resolveHints = analyzeMiddleware(workingDir, customSegments);
      const orderedSegments = segments.filter((s) => s !== 'commerce');
      const pzSegmentsBlock = generatePzSegmentsConfig(
        orderedSegments,
        resolveHints
      );

      content = content.replace(
        /(\n};\s*$)/,
        `,\n  ${pzSegmentsBlock}$1`
      );

      if (!content.includes('pzSegments')) {
        content = content.replace(
          /(};\s*$)/,
          `  ${pzSegmentsBlock},\n$1`
        );
      }
    }

    fs.writeFileSync(settingsPath, content);

    console.log('Updated settings.js:');
    console.log('  - usePzSegment: true');
    if (customSegments.length > 0) {
      const resolveHints = analyzeMiddleware(workingDir, customSegments);
      console.log(
        `  - pzSegments with segments: [${segments.filter((s) => s !== 'commerce').join(', ')}]`
      );
      for (const seg of customSegments) {
        const hint = resolveHints[seg];
        if (hint && hint.source !== 'unknown') {
          console.log(
            `    ${bold(seg)}: resolve from ${hint.source}`
          );
        } else {
          console.log(
            yellow(
              `    ${seg}: resolve source unknown — fill in the resolve function manually`
            )
          );
        }
      }
    }
  } else {
    console.log(
      yellow(
        'Warning: settings.js not found. Add usePzSegment: true manually.'
      )
    );
  }

  // Clean up middleware.ts rewrite block since url is now a built-in segment
  if (segments.includes('url') || customSegments.length > 0) {
    const cleaned = cleanupMiddleware(workingDir);
    if (cleaned) {
      console.log('Cleaned middleware.ts: removed rewrite block (now handled by built-in url segment)');
    }
  }

  // Update path references in tsconfig, imports, etc.
  const updatedFiles = updatePathReferences(workingDir, segments);
  if (updatedFiles.length > 0) {
    console.log(`Updated path references in ${updatedFiles.length} file(s):`);
    updatedFiles.forEach((f) => console.log(`  - ${f}`));
  }

  // Update params.locale/currency/url usages to parsePzParams
  const paramsUpdated = updateParamsUsage(workingDir);
  if (paramsUpdated.length > 0) {
    console.log(
      `Updated params usage in ${paramsUpdated.length} file(s):`
    );
    paramsUpdated.forEach((f) => console.log(`  - ${f}`));
  }

  console.log(green('\nMigration completed successfully!\n'));
  console.log('Next steps:');
  console.log(`  1. Review changes with ${bold('git diff')}`);
  console.log(`  2. Build with ${bold('yarn build')}`);
  console.log('');
};

module.exports = {
  transform
};
