import { Hype } from '@because/hype'
import { define, stylesToCSS } from '@because/forge'
import { baseStyles, initScript, theme } from '@because/toes/tools'
import { readFileSync, writeFileSync, existsSync } from 'fs'
import { join } from 'path'

const APPS_DIR = process.env.APPS_DIR!

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

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

const Header = define('Header', {
  marginBottom: '20px',
  display: 'flex',
  justifyContent: 'space-between',
  alignItems: 'center',
})

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

const AppName = define('AppName', {
  color: theme('colors-textMuted'),
  fontSize: '14px',
})

const TodoList = define('TodoList', {
  listStyle: 'none',
  padding: 0,
  margin: 0,
})

const TodoSection = define('TodoSection', {
  marginBottom: '24px',
})

const SectionTitle = define('SectionTitle', {
  fontSize: '16px',
  fontWeight: 600,
  color: theme('colors-textMuted'),
  marginBottom: '12px',
  paddingBottom: '8px',
  borderBottom: `1px solid ${theme('colors-border')}`,
})

const TodoItemStyle = define('TodoItem', {
  display: 'flex',
  alignItems: 'flex-start',
  padding: '8px 0',
  gap: '10px',
  selectors: {
    '& input[type="checkbox"]': {
      marginTop: '3px',
      width: '18px',
      height: '18px',
      cursor: 'pointer',
    },
    '& label': {
      flex: 1,
      cursor: 'pointer',
      lineHeight: '1.5',
    },
  },
})

const doneClass = 'todo-done'

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

const successClass = 'msg-success'
const errorClass = 'msg-error'

const SaveButton = define('SaveButton', {
  base: 'button',
  backgroundColor: theme('colors-primary'),
  color: theme('colors-primaryText'),
  border: 'none',
  padding: '8px 16px',
  borderRadius: theme('radius-md'),
  cursor: 'pointer',
  fontSize: '14px',
  fontWeight: 500,
  states: {
    ':hover': {
      opacity: 0.9,
    },
    ':disabled': {
      opacity: 0.5,
      cursor: 'not-allowed',
    },
  },
})

const AddForm = define('AddForm', {
  display: 'flex',
  gap: '10px',
  marginBottom: '20px',
})

const AddInput = define('AddInput', {
  base: 'input',
  flex: 1,
  padding: '8px 12px',
  border: `1px solid ${theme('colors-border')}`,
  borderRadius: theme('radius-md'),
  fontSize: '14px',
  backgroundColor: theme('colors-bg'),
  color: theme('colors-text'),
  states: {
    ':focus': {
      outline: 'none',
      borderColor: theme('colors-primary'),
    },
  },
})

const todoStyles = `
.${doneClass} {
  color: ${theme('colors-done')};
  text-decoration: line-through;
}
.${successClass} {
  padding: 12px 16px;
  border-radius: ${theme('radius-md')};
  margin-bottom: 16px;
  background-color: ${theme('colors-successBg')};
  color: ${theme('colors-success')};
}
.${errorClass} {
  padding: 12px 16px;
  border-radius: ${theme('radius-md')};
  margin-bottom: 16px;
  background-color: ${theme('colors-bgElement')};
  color: ${theme('colors-error')};
}
`

interface TodoEntry {
  text: string
  done: boolean
}

interface ParsedTodo {
  title: string
  items: TodoEntry[]
}

function parseTodoFile(content: string): ParsedTodo {
  const lines = content.split('\n')
  let title = 'TODO'
  const items: TodoEntry[] = []

  for (const line of lines) {
    const trimmed = line.trim()
    if (!trimmed) continue

    if (trimmed.startsWith('# ')) {
      title = trimmed.slice(2)
    } else if (trimmed.startsWith('[x] ') || trimmed.startsWith('[X] ')) {
      items.push({ text: trimmed.slice(4), done: true })
    } else if (trimmed.startsWith('[ ] ')) {
      items.push({ text: trimmed.slice(4), done: false })
    }
  }

  return { title, items }
}

function serializeTodo(todo: ParsedTodo): string {
  const lines = [`# ${todo.title}`]
  for (const item of todo.items) {
    lines.push(item.done ? `[x] ${item.text}` : `[ ] ${item.text}`)
  }
  return lines.join('\n') + '\n'
}

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

app.get('/', async c => {
  const appName = c.req.query('app')
  const message = c.req.query('message')
  const messageType = c.req.query('type') as 'success' | 'error' | undefined

  if (!appName) {
    return c.html(
      <html>
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>TODO</title>
          <link rel="stylesheet" href="/styles.css" />
        </head>
        <body>
          <script dangerouslySetInnerHTML={{ __html: initScript }} />
          <Container>
            <Header>
              <Title>TODO</Title>
            </Header>
            <Error>Select an app to view its TODO list</Error>
          </Container>
        </body>
      </html>
    )
  }

  const todoPath = join(APPS_DIR, appName, 'current', 'TODO.txt')

  let todo: ParsedTodo
  if (existsSync(todoPath)) {
    const content = readFileSync(todoPath, 'utf-8')
    todo = parseTodoFile(content)
  } else {
    todo = { title: `${appName} TODO`, items: [] }
  }

  const pendingItems = todo.items.filter(i => !i.done)

  return c.html(
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>{todo.title}</title>
        <link rel="stylesheet" href="/styles.css" />
      </head>
      <body>
        <script dangerouslySetInnerHTML={{ __html: initScript }} />
        <Container>
          <Header>
            <div>
              <Title>{todo.title}</Title>
              <AppName>{appName}/TODO.txt</AppName>
            </div>
          </Header>

          {message && (
            <div class={messageType === 'success' ? successClass : errorClass}>
              {message}
            </div>
          )}

          <form action="/add" method="post">
            <input type="hidden" name="app" value={appName} />
            <AddForm>
              <AddInput type="text" name="text" placeholder="Add a new todo..." required />
              <SaveButton type="submit">Add</SaveButton>
            </AddForm>
          </form>

          {todo.items.length > 0 && (
            <TodoSection>
              <SectionTitle>
                {pendingItems.length === 0 ? 'All done!' : `Pending (${pendingItems.length})`}
              </SectionTitle>
              <TodoList>
                {todo.items.map((item, i) => (
                  <TodoItemStyle key={i}>
                    <form action="/toggle" method="post" style={{ display: 'contents' }}>
                      <input type="hidden" name="app" value={appName} />
                      <input type="hidden" name="index" value={i.toString()} />
                      <input
                        type="checkbox"
                        id={`item-${i}`}
                        checked={item.done}
                        onchange="this.form.submit()"
                      />
                      <label for={`item-${i}`} class={item.done ? doneClass : ''}>
                        {item.text}
                      </label>
                    </form>
                  </TodoItemStyle>
                ))}
              </TodoList>
            </TodoSection>
          )}

          {todo.items.length === 0 && (
            <TodoSection>
              <SectionTitle>No todos yet</SectionTitle>
              <p style={{ color: theme('colors-textMuted') }}>Add your first todo above!</p>
            </TodoSection>
          )}
        </Container>
      </body>
    </html>
  )
})

app.post('/toggle', async c => {
  const form = await c.req.formData()
  const appName = form.get('app') as string
  const index = parseInt(form.get('index') as string, 10)

  const todoPath = join(APPS_DIR, appName, 'current', 'TODO.txt')

  let todo: ParsedTodo
  if (existsSync(todoPath)) {
    const content = readFileSync(todoPath, 'utf-8')
    todo = parseTodoFile(content)
  } else {
    return c.redirect(`/?app=${appName}`)
  }

  if (index >= 0 && index < todo.items.length) {
    todo.items[index].done = !todo.items[index].done
  }

  try {
    writeFileSync(todoPath, serializeTodo(todo))
    return c.redirect(`/?app=${appName}`)
  } catch {
    return c.redirect(`/?app=${appName}&message=${encodeURIComponent('Failed to save')}&type=error`)
  }
})

app.post('/add', async c => {
  const form = await c.req.formData()
  const appName = form.get('app') as string
  const text = (form.get('text') as string).trim()

  if (!text) {
    return c.redirect(`/?app=${appName}`)
  }

  const todoPath = join(APPS_DIR, appName, 'current', 'TODO.txt')

  let todo: ParsedTodo
  if (existsSync(todoPath)) {
    const content = readFileSync(todoPath, 'utf-8')
    todo = parseTodoFile(content)
  } else {
    todo = { title: `${appName} TODO`, items: [] }
  }

  todo.items.push({ text, done: false })

  try {
    writeFileSync(todoPath, serializeTodo(todo))
    return c.redirect(`/?app=${appName}`)
  } catch {
    return c.redirect(`/?app=${appName}&message=${encodeURIComponent('Failed to add')}&type=error`)
  }
})

export default app.defaults
