import { Hype } from '@because/hype'
import { createThemes, define, stylesToCSS } from '@because/forge'
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 })

// Theme
const theme = createThemes({
  light: {
    bg: '#ffffff',
    text: '#1a1a1a',
    textMuted: '#666666',
    border: '#dddddd',
    borderSubtle: '#eeeeee',
    borderStrong: '#333333',
    hover: '#f5f5f5',
    surface: '#f5f5f5',
    link: '#0066cc',
    icon: '#666666',
    codeBg: '#fafafa',
    error: '#d32f2f',
    errorBg: '#ffebee',
  },
  dark: {
    bg: '#1a1a1a',
    text: '#e5e5e5',
    textMuted: '#999999',
    border: '#404040',
    borderSubtle: '#333333',
    borderStrong: '#555555',
    hover: '#2a2a2a',
    surface: '#252525',
    link: '#5c9eff',
    icon: '#888888',
    codeBg: '#1e1e1e',
    error: '#ff6b6b',
    errorBg: '#3d1f1f',
  },
})

// Styles
const Container = define('Container', {
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
  padding: '20px',
  maxWidth: '1200px',
  margin: '0 auto',
  color: theme('text'),
})

const Header = define('Header', {
  marginBottom: '20px',
  paddingBottom: '10px',
  borderBottom: `2px solid ${theme('borderStrong')}`,
})

const Title = define('Title', {
  margin: 0,
  fontSize: '24px',
  fontWeight: 'bold',
})

const Subtitle = define('Subtitle', {
  color: theme('textMuted'),
  fontSize: '18px',
  marginTop: '5px',
})

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

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

const FileLink = define('FileLink', {
  base: 'a',
  textDecoration: 'none',
  color: theme('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('icon'),
})

const CodeBlock = define('CodeBlock', {
  margin: '20px 0',
  border: `1px solid ${theme('border')}`,
  borderRadius: '4px',
  overflow: 'auto',
  selectors: {
    '& pre': {
      margin: 0,
      padding: '15px',
      overflow: 'auto',
      whiteSpace: 'pre',
      backgroundColor: theme('codeBg'),
    },
    '& pre code': {
      whiteSpace: 'pre',
      fontFamily: 'monospace',
    },
  },
})

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

const ErrorBox = define('ErrorBox', {
  color: theme('error'),
  padding: '20px',
  backgroundColor: theme('errorBg'),
  borderRadius: '4px',
  margin: '20px 0',
})

const BackLink = define('BackLink', {
  base: 'a',
  textDecoration: 'none',
  color: theme('link'),
  display: 'inline-flex',
  alignItems: 'center',
  gap: '5px',
  marginBottom: '15px',
  states: {
    ':hover': {
      textDecoration: 'underline',
    },
  },
})

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>
)

const initScript = `
(function() {
  var theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  document.body.setAttribute('data-theme', theme);
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
    document.body.setAttribute('data-theme', e.matches ? 'dark' : 'light');
  });
  function sendHeight() {
    window.parent.postMessage({ type: 'resize-iframe', height: document.documentElement.scrollHeight }, '*');
  }
  sendHeight();
  new ResizeObserver(sendHeight).observe(document.body);
  window.addEventListener('load', sendHeight);
})();
`

const baseStyles = `
body {
  background: ${theme('bg')};
  margin: 0;
}
`

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

function Layout({ title, subtitle, 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: initScript }} />
        <Container>
          <Header>
            <Title>Code Browser</Title>
            {subtitle && <Subtitle>{subtitle}</Subtitle>}
          </Header>
          {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',
}))

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)
  })
}

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 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, 'current')

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

  const fullPath = join(appPath, filePath)
  let fileStats

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

  const parentPath = filePath.split('/').slice(0, -1).join('/')
  const backUrl = `/?app=${appName}${parentPath ? `&file=${parentPath}` : ''}`

  if (fileStats.isFile()) {
    const content = readFileSync(fullPath, 'utf-8')
    const language = getLanguage(basename(fullPath))

    return c.html(
      <Layout title={`${appName}/${filePath}`} subtitle={`${appName}/${filePath}`} highlight>
        <BackLink href={backUrl}>← Back</BackLink>
        <CodeBlock>
          <CodeHeader>{basename(fullPath)}</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}` : ''}`} subtitle={`${appName}${filePath ? `/${filePath}` : ''}`}>
      {filePath && <BackLink href={backUrl}>← Back</BackLink>}
      <FileList>
        {files.map(file => (
          <FileItem>
            <FileLink href={`/?app=${appName}&file=${file.path}`}>
              {file.isDirectory ? <FolderIcon /> : <FileIconSvg />}
              <span>{file.name}</span>
            </FileLink>
          </FileItem>
        ))}
      </FileList>
    </Layout>
  )
})

export default app.defaults
