import { Hype } from '@because/hype'
import { define, stylesToCSS } from '@because/forge'
import { baseStyles, initScript, theme } from '@because/toes/tools'
import { readdir, stat } from 'fs/promises'
import { readFileSync } from 'fs'
import { join, extname, basename } from 'path'
import type { Child } from 'hono/jsx'

const APPS_DIR = process.env.APPS_DIR!

const app = new Hype({ prettyHTML: false })

const Container = define('Container', {
  fontFamily: theme('fonts-sans'),
  padding: '20px',
  paddingTop: 0,
  maxWidth: '1200px',
  margin: '0 auto',
  color: theme('colors-text'),
})

const FileList = define('FileList', {
  listStyle: 'none',
  padding: 0,
  margin: '20px 0',
  border: `1px solid ${theme('colors-border')}`,
  borderRadius: theme('radius-md'),
  overflow: 'hidden',
})

const FileItem = define('FileItem', {
  padding: '10px 15px',
  borderBottom: `1px solid ${theme('colors-border')}`,
  states: {
    ':last-child': {
      borderBottom: 'none',
    },
    ':hover': {
      backgroundColor: theme('colors-bgHover'),
    },
  }
})

const FileLink = define('FileLink', {
  base: 'a',
  textDecoration: 'none',
  color: theme('colors-link'),
  display: 'flex',
  alignItems: 'center',
  gap: '8px',
  states: {
    ':hover': {
      textDecoration: 'underline',
    },
  }
})

const FileIcon = define('FileIcon', {
  base: 'svg',
  width: '18px',
  height: '18px',
  flexShrink: 0,
  fill: theme('colors-textMuted'),
})

const CodeBlock = define('CodeBlock', {
  margin: '20px 0',
  border: `1px solid ${theme('colors-border')}`,
  borderRadius: theme('radius-md'),
  overflowX: 'auto',
  selectors: {
    '& pre': {
      margin: 0,
      padding: '15px',
      whiteSpace: 'pre',
      backgroundColor: theme('colors-bgSubtle'),
    },
    '& pre code': {
      whiteSpace: 'pre',
      fontFamily: theme('fonts-mono'),
    },
  },
})

const CodeHeader = define('CodeHeader', {
  padding: '10px 15px',
  backgroundColor: theme('colors-bgElement'),
  borderBottom: `1px solid ${theme('colors-border')}`,
  fontWeight: 'bold',
  fontSize: '14px',
})

const ErrorBox = define('ErrorBox', {
  color: theme('colors-error'),
  padding: '20px',
  backgroundColor: theme('colors-bgElement'),
  borderRadius: theme('radius-md'),
  margin: '20px 0',
})

const Breadcrumb = define('Breadcrumb', {
  display: 'flex',
  alignItems: 'center',
  gap: '6px',
  fontSize: '14px',
  marginBottom: '15px',
  flexWrap: 'wrap',
})

const BreadcrumbLink = define('BreadcrumbLink', {
  base: 'a',
  textDecoration: 'none',
  color: theme('colors-link'),
  states: {
    ':hover': {
      textDecoration: 'underline',
    },
  },
})

const BreadcrumbSeparator = define('BreadcrumbSeparator', {
  color: theme('colors-textMuted'),
})

const BreadcrumbCurrent = define('BreadcrumbCurrent', {
  color: theme('colors-text'),
  fontWeight: 500,
})

const MediaContainer = define('MediaContainer', {
  margin: '20px 0',
  border: `1px solid ${theme('colors-border')}`,
  borderRadius: theme('radius-md'),
  overflow: 'hidden',
  backgroundColor: theme('colors-bgSubtle'),
})

const MediaHeader = define('MediaHeader', {
  padding: '10px 15px',
  backgroundColor: theme('colors-bgElement'),
  borderBottom: `1px solid ${theme('colors-border')}`,
  fontWeight: 'bold',
  fontSize: '14px',
  display: 'flex',
  justifyContent: 'space-between',
  alignItems: 'center',
})

const MediaContent = define('MediaContent', {
  padding: '20px',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
})

const ImagePreview = define('ImagePreview', {
  base: 'img',
  maxWidth: '100%',
  maxHeight: '600px',
  objectFit: 'contain',
})

const AudioPlayer = define('AudioPlayer', {
  base: 'audio',
  width: '100%',
  maxWidth: '500px',
})

const VideoPlayer = define('VideoPlayer', {
  base: 'video',
  maxWidth: '100%',
  maxHeight: '600px',
})

const DownloadButton = define('DownloadButton', {
  base: 'a',
  display: 'inline-flex',
  alignItems: 'center',
  gap: '6px',
  padding: '8px 16px',
  backgroundColor: theme('colors-primary'),
  color: 'white',
  textDecoration: 'none',
  borderRadius: theme('radius-md'),
  fontSize: '14px',
  states: {
    ':hover': {
      opacity: 0.9,
    },
  },
})

const FolderIcon = () => (
  <FileIcon viewBox="0 0 24 24">
    <path d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" />
  </FileIcon>
)

const FileIconSvg = () => (
  <FileIcon viewBox="0 0 24 24">
    <path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 2l5 5h-5V4zM6 20V4h6v6h6v10H6z" />
  </FileIcon>
)

interface LayoutProps {
  title: string
  children: Child
  highlight?: boolean
}

const fileMemoryScript = `
(function() {
  var params = new URLSearchParams(window.location.search);
  var app = params.get('app');
  var file = params.get('file');
  var version = params.get('version') || 'current';
  if (!app) return;
  var key = 'code-app:' + app + ':' + version + ':file';
  if (params.has('file')) {
    // Explicit file param (even empty) - save it
    if (file) localStorage.setItem(key, file);
    else localStorage.removeItem(key);
  } else {
    // No file param - restore saved location
    var saved = localStorage.getItem(key);
    if (saved) {
      var url = '/?app=' + encodeURIComponent(app);
      if (version !== 'current') url += '&version=' + encodeURIComponent(version);
      url += '&file=' + encodeURIComponent(saved);
      window.location.replace(url);
    }
  }
})();
`

function Layout({ title, children, highlight }: LayoutProps) {
  return (
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>{title}</title>
        <link rel="stylesheet" href="/styles.css" />
        {highlight && (
          <>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" media="(prefers-color-scheme: light)" />
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="(prefers-color-scheme: dark)" />
            <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
          </>
        )}
      </head>
      <body>
        <script dangerouslySetInnerHTML={{ __html: fileMemoryScript }} />
        <script dangerouslySetInnerHTML={{ __html: initScript }} />
        <Container>
          {children}
        </Container>
        {highlight && <script dangerouslySetInnerHTML={{ __html: 'hljs.highlightAll();' }} />}
      </body>
    </html>
  )
}

app.get('/styles.css', c => c.text(baseStyles + stylesToCSS(), 200, {
  'Content-Type': 'text/css; charset=utf-8',
}))

app.get('/raw', async c => {
  const appName = c.req.query('app')
  const version = c.req.query('version') || 'current'
  const filePath = c.req.query('file')

  if (!appName || !filePath) {
    return c.text('Missing app or file parameter', 400)
  }

  const fullPath = join(APPS_DIR, appName, version, filePath)
  const file = Bun.file(fullPath)

  if (!await file.exists()) {
    return c.text(`File not found: ${fullPath}`, 404)
  }

  return new Response(file)
})

async function listFiles(appPath: string, subPath: string = '') {
  const fullPath = join(appPath, subPath)
  const entries = await readdir(fullPath, { withFileTypes: true })

  const items = entries.map(entry => ({
    name: entry.name,
    isDirectory: entry.isDirectory(),
    path: subPath ? `${subPath}/${entry.name}` : entry.name,
  }))

  return items.sort((a, b) => {
    if (a.isDirectory !== b.isDirectory) {
      return a.isDirectory ? -1 : 1
    }
    return a.name.localeCompare(b.name)
  })
}

interface BreadcrumbProps {
  appName: string
  filePath: string
  versionParam: string
}

function PathBreadcrumb({ appName, filePath, versionParam }: BreadcrumbProps) {
  const parts = filePath ? filePath.split('/').filter(Boolean) : []
  const crumbs: { name: string; path: string }[] = []

  let currentPath = ''
  for (const part of parts) {
    currentPath = currentPath ? `${currentPath}/${part}` : part
    crumbs.push({ name: part, path: currentPath })
  }

  return (
    <Breadcrumb>
      {crumbs.length > 0 ? (
        <BreadcrumbLink href={`/?app=${appName}${versionParam}&file=`}>{appName}</BreadcrumbLink>
      ) : (
        <BreadcrumbCurrent>{appName}</BreadcrumbCurrent>
      )}
      {crumbs.map((crumb, i) => (
        <>
          <BreadcrumbSeparator>/</BreadcrumbSeparator>
          {i === crumbs.length - 1 ? (
            <BreadcrumbCurrent>{crumb.name}</BreadcrumbCurrent>
          ) : (
            <BreadcrumbLink href={`/?app=${appName}${versionParam}&file=${crumb.path}`}>{crumb.name}</BreadcrumbLink>
          )}
        </>
      ))}
    </Breadcrumb>
  )
}

const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.bmp'])
const AUDIO_EXTS = new Set(['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac'])
const VIDEO_EXTS = new Set(['.mp4', '.webm', '.mov', '.avi'])
const BINARY_EXTS = new Set(['.pdf', '.zip', '.tar', '.gz', '.exe', '.dmg', '.woff', '.woff2', '.ttf', '.otf', '.eot'])

type FileType = 'text' | 'image' | 'audio' | 'video' | 'binary'

function getFileType(filename: string): FileType {
  const ext = extname(filename).toLowerCase()
  if (IMAGE_EXTS.has(ext)) return 'image'
  if (AUDIO_EXTS.has(ext)) return 'audio'
  if (VIDEO_EXTS.has(ext)) return 'video'
  if (BINARY_EXTS.has(ext)) return 'binary'
  return 'text'
}

function getLanguage(filename: string): string {
  const ext = extname(filename).toLowerCase()
  const langMap: Record<string, string> = {
    '.js': 'javascript',
    '.jsx': 'javascript',
    '.ts': 'typescript',
    '.tsx': 'typescript',
    '.json': 'json',
    '.css': 'css',
    '.html': 'html',
    '.md': 'markdown',
    '.sh': 'bash',
    '.yml': 'yaml',
    '.yaml': 'yaml',
  }
  return langMap[ext] || 'plaintext'
}


app.get('/', async c => {
  const appName = c.req.query('app')
  const version = c.req.query('version') || 'current'
  const filePath = c.req.query('file') || ''

  if (!appName) {
    return c.html(
      <Layout title="Code Browser">
        <ErrorBox>Please specify an app name with ?app=&lt;name&gt;</ErrorBox>
      </Layout>
    )
  }

  const appPath = join(APPS_DIR, appName, version)

  try {
    await stat(appPath)
  } catch {
    return c.html(
      <Layout title="Code Browser">
        <ErrorBox>App "{appName}" (version: {version}) not found</ErrorBox>
      </Layout>
    )
  }

  const fullPath = join(appPath, filePath)
  let fileStats

  try {
    fileStats = await stat(fullPath)
  } catch {
    return c.html(
      <Layout title="Code Browser">
        <ErrorBox>Path "{filePath}" not found</ErrorBox>
      </Layout>
    )
  }

  const versionParam = version !== 'current' ? `&version=${version}` : ''

  if (fileStats.isFile()) {
    const filename = basename(fullPath)
    const fileType = getFileType(filename)
    const rawUrl = `/raw?app=${appName}${versionParam}&file=${filePath}`
    const downloadUrl = `${rawUrl}&download=1`

    if (fileType === 'image') {
      return c.html(
        <Layout title={`${appName}/${filePath}`}>
          <PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
          <MediaContainer>
            <MediaHeader>
              <span>{filename}</span>
              <DownloadButton href={downloadUrl}>Download</DownloadButton>
            </MediaHeader>
            <MediaContent>
              <ImagePreview src={rawUrl} alt={filename} />
            </MediaContent>
          </MediaContainer>
        </Layout>
      )
    }

    if (fileType === 'audio') {
      return c.html(
        <Layout title={`${appName}/${filePath}`}>
          <PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
          <MediaContainer>
            <MediaHeader>
              <span>{filename}</span>
              <DownloadButton href={downloadUrl}>Download</DownloadButton>
            </MediaHeader>
            <MediaContent>
              <AudioPlayer controls src={rawUrl} />
            </MediaContent>
          </MediaContainer>
        </Layout>
      )
    }

    if (fileType === 'video') {
      return c.html(
        <Layout title={`${appName}/${filePath}`}>
          <PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
          <MediaContainer>
            <MediaHeader>
              <span>{filename}</span>
              <DownloadButton href={downloadUrl}>Download</DownloadButton>
            </MediaHeader>
            <MediaContent>
              <VideoPlayer controls src={rawUrl} />
            </MediaContent>
          </MediaContainer>
        </Layout>
      )
    }

    if (fileType === 'binary') {
      return c.redirect(downloadUrl)
    }

    // Text file - show with syntax highlighting
    const content = readFileSync(fullPath, 'utf-8')
    const language = getLanguage(filename)

    return c.html(
      <Layout title={`${appName}/${filePath}`} highlight>
        <PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
        <CodeBlock>
          <CodeHeader>{filename}</CodeHeader>
          <pre><code class={`language-${language}`}>{content}</code></pre>
        </CodeBlock>
      </Layout>
    )
  }

  const files = await listFiles(appPath, filePath)

  return c.html(
    <Layout title={`${appName}${filePath ? `/${filePath}` : ''}`}>
      <PathBreadcrumb appName={appName} filePath={filePath} versionParam={versionParam} />
      <FileList>
        {files.map(file => (
          <FileItem>
            <FileLink href={`/?app=${appName}${versionParam}&file=${file.path}`}>
              {file.isDirectory ? <FolderIcon /> : <FileIconSvg />}
              <span>{file.name}</span>
            </FileLink>
          </FileItem>
        ))}
      </FileList>
    </Layout>
  )
})

export default app.defaults
