import { defineCommand, runMain, showUsage } from 'citty'
import { readFileSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import colors from 'picocolors'

function getPackageVersion() {
  let dirname
  if (typeof __dirname !== 'undefined') {
    // CommonJS
    dirname = __dirname
  } else {
    // ESM
    dirname = path.dirname(fileURLToPath(import.meta.url))
  }
  const packagePath = path.join(dirname, '..', '..', 'package.json')
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'))
  return packageJson.version
}

const version = getPackageVersion()

const DOCS_BASE = 'https://onestack.dev/docs'

const docsLinks = {
  dev: `${DOCS_BASE}/one-dev`,
  build: `${DOCS_BASE}/one-build`,
  serve: `${DOCS_BASE}/one-serve`,
  prebuild: `${DOCS_BASE}/guides-ios-native`,
  'run:ios': `${DOCS_BASE}/guides-ios-native`,
  'run:android': `${DOCS_BASE}/guides-ios-native`,
  clean: `${DOCS_BASE}/configuration`,
  patch: `${DOCS_BASE}/configuration`,
  'generate-routes': `${DOCS_BASE}/routing-typed-routes`,
  typegen: `${DOCS_BASE}/routing-typed-routes`,
} as const

function withDocsLink(description: string, command: keyof typeof docsLinks): string {
  return `${description}\n\nDocs: ${docsLinks[command]}`
}

if (path.sep !== '/') {
  console.warn(
    colors.bgYellow('WARNING: UNSUPPORTED OS') +
      colors.yellow(
        ' - It appears you’re using Windows, which is currently not supported. You may experience unexpected issues.'
      )
  )
}

const modes = {
  development: 'development',
  production: 'production',
} as const

// citty returns an array when the same arg is passed multiple times
// use the last value (standard CLI behavior - later args override earlier)
function lastValue<T>(arg: T | T[]): T | undefined {
  if (Array.isArray(arg)) {
    return arg[arg.length - 1]
  }
  return arg
}

const dev = defineCommand({
  meta: {
    name: 'dev',
    version: version,
    description: withDocsLink('Start the dev server', 'dev'),
  },
  args: {
    clean: {
      type: 'boolean',
    },
    host: {
      type: 'string',
    },
    port: {
      type: 'string',
    },
    https: {
      type: 'boolean',
    },
    mode: {
      type: 'string',
      description:
        'If set to "production" you can run the development server but serve the production bundle',
    },
    'debug-bundle': {
      type: 'string',
      description: `Will output the bundle to a temp file and then serve it from there afterwards allowing you to easily edit the bundle to debug problems.`,
    },
    debug: {
      type: 'string',
      description: `Pass debug args to Vite`,
    },
    'extra-config': {
      type: 'string',
      description: `Path to an extra vite config file to merge on top of the project config`,
    },
  },
  async run({ args }) {
    const { dev } = await import('./cli/dev')
    await dev({
      ...args,
      port: lastValue(args.port),
      host: lastValue(args.host),
      debugBundle: lastValue(args['debug-bundle']),
      mode: modes[lastValue(args.mode) as keyof typeof modes],
      extraConfig: lastValue(args['extra-config']),
    })
  },
})

const buildCommand = defineCommand({
  meta: {
    name: 'build',
    version: version,
    description: withDocsLink('Build your app', 'build'),
  },
  args: {
    step: {
      type: 'string',
      required: false,
    },
    // limit the pages built
    only: {
      type: 'string',
      required: false,
    },
    platform: {
      type: 'string',
      description: `One of: web, ios, android`,
      default: 'web',
      required: false,
    },
    'skip-env': {
      type: 'boolean',
      description: `Skip loading .env files during build`,
      required: false,
    },
  },
  async run({ args }) {
    const { build } = await import('./cli/build')

    const platforms = {
      ios: 'ios',
      web: 'web',
      android: 'android',
    } as const

    if (args.platform && !platforms[args.platform]) {
      throw new Error(`Invalid platform: ${args.platform}`)
    }

    const platform = platforms[args.platform as keyof typeof platforms] || 'web'

    await build({
      only: args.only,
      platform,
      step: args.step,
      skipEnv: args['skip-env'],
    })
    // TODO somewhere just before 1787f241b79 this stopped exiting, must have some hanging task
    process.exit(0)
  },
})

const serveCommand = defineCommand({
  meta: {
    name: 'serve',
    version: version,
    description: withDocsLink('Serve a built app for production', 'serve'),
  },
  args: {
    host: {
      type: 'string',
    },
    port: {
      type: 'string',
    },
    compress: {
      type: 'boolean',
    },
    loadEnv: {
      type: 'boolean',
    },
    outDir: {
      type: 'string',
      description: 'Build output directory (default: dist)',
    },
    cluster: {
      type: 'string',
      description: 'Enable cluster mode with N workers (default: cpu count)',
    },
  },
  async run({ args }) {
    const { serve } = await import('./serve')
    const port = lastValue(args.port)
    const host = lastValue(args.host)
    const clusterArg = lastValue(args.cluster)
    await serve({
      port: port ? +port : undefined,
      host,
      compress: args.compress,
      loadEnv: !!args.loadEnv,
      outDir: lastValue(args.outDir),
      cluster: clusterArg !== undefined ? (clusterArg ? +clusterArg : true) : undefined,
    })
  },
})

const prebuild = defineCommand({
  meta: {
    name: 'prebuild',
    version: version,
    description: withDocsLink('Prebuild native project', 'prebuild'),
  },
  args: {
    platform: {
      type: 'string',
      description: 'ios or android',
    },

    expo: {
      type: 'boolean',
      description: 'expo or non-expo folders',
      default: true,
    },

    'no-install': {
      type: 'boolean',
      description: 'skip installing native dependencies',
      default: false,
    },
  },
  async run({ args }) {
    if (args.install === false) args['no-install'] = true // citty seems to convert --no-install to install: false, leaving no-install as default

    const { run } = await import('./cli/prebuild')
    await run(args)
  },
})

const runIos = defineCommand({
  meta: {
    name: 'run:ios',
    version: version,
    description: withDocsLink('Run the iOS app', 'run:ios'),
  },
  args: {},
  async run({ args }) {
    const { run } = await import('./cli/runIos')
    await run(args)
  },
})

const runAndroid = defineCommand({
  meta: {
    name: 'run:android',
    version: version,
    description: withDocsLink('Run the Android app', 'run:android'),
  },
  args: {},
  async run({ args }) {
    const { run } = await import('./cli/runAndroid')
    await run(args)
  },
})

const clean = defineCommand({
  meta: {
    name: 'clean',
    version: '0.0.0',
    description: withDocsLink('Clean build folders', 'clean'),
  },
  args: {},
  async run() {
    const { clean: vxrnClean } = await import('vxrn')
    await vxrnClean({
      root: process.cwd(),
    })
  },
})

const patch = defineCommand({
  meta: {
    name: 'patch',
    version: '0.0.0',
    description: withDocsLink('Apply package patches', 'patch'),
  },
  args: {
    force: {
      type: 'boolean',
      description: 'Force re-apply all patches',
    },
  },
  async run({ args }) {
    const { run } = await import('./cli/patch')
    await run(args)
  },
})

const generateRoutes = defineCommand({
  meta: {
    name: 'generate-routes',
    version: version,
    description: withDocsLink(
      'Generate route type definitions (routes.d.ts)',
      'generate-routes'
    ),
  },
  args: {
    appDir: {
      type: 'string',
      description: 'Path to app directory (default: "app")',
    },
    typed: {
      type: 'string',
      description:
        'Auto-generate route helpers. Options: "type" (type-only helpers) or "runtime" (runtime helpers)',
    },
  },
  async run({ args }) {
    const { run } = await import('./cli/generateRoutes')
    await run(args)
  },
})

const typegen = defineCommand({
  meta: {
    name: 'typegen',
    version: version,
    description: withDocsLink(
      'Generate routes.d.ts (alias for generate-routes)',
      'typegen'
    ),
  },
  args: {
    appDir: {
      type: 'string',
      description: 'Path to app directory (default: "app")',
    },
    typed: {
      type: 'string',
      description:
        'Auto-generate route helpers. Options: "type" (type-only helpers) or "runtime" (runtime helpers)',
    },
  },
  async run({ args }) {
    const { run } = await import('./cli/generateRoutes')
    await run(args)
  },
})

const daemonCommand = defineCommand({
  meta: {
    name: 'daemon',
    version: version,
    description: 'Multi-app development server proxy',
  },
  args: {
    subcommand: {
      type: 'positional',
      description: 'Subcommand: start, stop, status, route (default: start)',
      required: false,
    },
    port: {
      type: 'string',
      description: 'Port to listen on (default: 8081)',
    },
    host: {
      type: 'string',
      description: 'Host to bind to (default: 0.0.0.0)',
    },
    app: {
      type: 'string',
      description: 'Bundle ID for route command',
    },
    slot: {
      type: 'string',
      description: 'Slot number for route command',
    },
    project: {
      type: 'string',
      description: 'Project path for route command',
    },
    tui: {
      type: 'boolean',
      description: 'Show TUI (default: true if TTY)',
    },
  },
  async run({ args }) {
    const { daemon } = await import('./cli/daemon')
    await daemon({
      ...args,
      port: lastValue(args.port),
      host: lastValue(args.host),
    })
  },
})

const subCommands = {
  dev,
  clean,
  build: buildCommand,
  prebuild,
  'run:ios': runIos,
  'run:android': runAndroid,
  patch,
  serve: serveCommand,
  'generate-routes': generateRoutes,
  typegen,
  daemon: daemonCommand,
}

// workaround for having sub-commands but also positional arg for naming in the create flow
const subMain = defineCommand({
  meta: {
    name: 'main',
    version: version,
    description: 'Welcome to One',
  },
  subCommands,
})

const main = defineCommand({
  meta: {
    name: 'main',
    version: version,
    description: 'Welcome to One',
  },
  args: {
    name: {
      type: 'positional',
      description: 'Folder name to place the app into',
      required: false,
    },
  },
  async run({ args }) {
    if (subCommands[args.name]) {
      // run sub command ourselves
      runMain(subMain)
      return
    }

    const { cliMain } = await import('./cli/main')
    await cliMain(args)
  },
})

// workaround for help with our workaround for sub-command + positional arg

const helpIndex = process.argv.indexOf('--help')
if (helpIndex > 0) {
  const subCommandName = process.argv[helpIndex - 1]
  const subCommand = subCommands[subCommandName]
  if (subCommand) {
    showUsage(subCommand)
  } else {
    showUsage(subMain)
  }
} else {
  runMain(main)
}
