package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const SharedCSS = `
  :root {
    color-scheme: dark;
    --tool-gap-sm: 8px;
    --tool-gap-md: 12px;
    --tool-gap-lg: 16px;
    --tool-radius: 5px;
    --tool-border: #333;
    --tool-panel: #252526;
  }
  * { box-sizing: border-box; }
  html { min-height: 100%; }
  body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background: #1e1e1e; color: #ddd;
    margin: 0; padding: 0;
  }
  .topbar {
    display: flex; align-items: center; gap: 8px;
    padding: 8px 16px; background: #252526; border-bottom: 1px solid #333;
    font-size: 13px;
  }
  .topbar .nav-link {
    background: transparent;
    color: #ddd;
    border: 0;
    padding: 5px 10px;
    border-radius: 4px;
    cursor: pointer;
    font: inherit;
  }
  .topbar .nav-link:hover { background: #2a2d2e; }
  .topbar .nav-link.active { background: #094771; color: #fff; }
  .beta-pill { margin-left: 4px; padding: 1px 4px; border: 1px solid #555; border-radius: 3px; font-size: 10px; opacity: 0.7; text-transform: uppercase; }
  .topbar-spacer { margin-left: auto; }
  .content { flex: 1; width: 100%; padding: 18px; max-width: 1100px; margin: 0 auto; }
  .app-footer {
    width: 100%;
    max-width: 1100px;
    margin: auto auto 0;
    padding: 8px 18px 14px;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 8px;
    color: #a7a7a7;
    font-size: 10px;
    border-top: 1px solid #333;
  }
  .beta-tools-toggle {
    justify-self: start;
    padding: 0;
    background: transparent;
    color: #a7a7a7;
    border: 0;
    cursor: pointer;
    font: inherit;
    opacity: 0.72;
    text-align: left;
  }
  .beta-tools-toggle:hover { background: transparent; opacity: 1; text-decoration: underline; }
  .app-footer-version { opacity: 0.75; text-align: center; white-space: nowrap; }
  .app-footer-link { justify-self: end; padding: 0; background: transparent; color: #a7a7a7; border: 0; cursor: pointer; font: inherit; opacity: 0.72; }
  .app-footer-link:hover { background: transparent; opacity: 1; text-decoration: underline; }
  h2 { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 600; }
  .tool-header { margin-bottom: var(--tool-gap-lg); }
  .tool-title { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 600; }
  .tool-description, .section-description, .muted { color: #a7a7a7; }
  .tool-description { margin: 5px 0 0; max-width: 780px; font-size: 12px; line-height: 1.45; }
  .tool-panel { margin-bottom: var(--tool-gap-lg); border: 1px solid var(--tool-border); border-radius: var(--tool-radius); background: rgba(128,128,128,0.08); overflow: hidden; }
  .panel-header { padding: 10px 12px; border-bottom: 1px solid var(--tool-border); background: var(--tool-panel); }
  .panel-title { margin: 0; color: #ddd; font-size: 12px; font-weight: 700; letter-spacing: 0.45px; text-transform: uppercase; }
  .panel-body { padding: 12px; }
  .control-group + .control-group { margin-top: var(--tool-gap-md); }
  .control-label { margin: 0 0 6px; color: #a7a7a7; font-size: 11px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase; }
  .file-row, .field-row, .action-row { display: flex; align-items: center; gap: var(--tool-gap-sm); flex-wrap: wrap; }
  .action-row { margin-top: var(--tool-gap-md); }
  input[type=text], input[type=date], select, textarea {
    box-sizing: border-box; padding: 6px 8px;
    background: #3c3c3c; color: #ddd;
    border: 1px solid #3c3c3c; border-radius: 2px;
    font-family: inherit; font-size: 13px;
  }
  input[type=text]:focus, input[type=date]:focus, select:focus, textarea:focus {
    outline: 1px solid #007fd4; border-color: #007fd4;
  }
  button {
    background: #0e639c; color: #fff;
    border: none; padding: 6px 14px; border-radius: 2px; cursor: pointer; font-size: 13px;
  }
  button:hover { background: #1177bb; }
  button.secondary { background: #3a3d41; color: #ddd; }
  button.secondary:hover { background: #45494e; }
  button.danger { background: transparent; color: #f48771; border: 1px solid #444; }
  button.danger:hover { background: rgba(244,135,113,0.14); }
  button:disabled { opacity: 0.5; cursor: not-allowed; }
  .status-panel, .results-panel, .result-card { border: 1px solid var(--tool-border); border-radius: var(--tool-radius); background: rgba(128,128,128,0.08); }
  .status-panel { min-height: 30px; margin-top: var(--tool-gap-md); padding: 8px 10px; white-space: pre-wrap; }
  .status-panel.is-busy { display: flex; align-items: center; gap: 8px; }
  .status-panel.is-busy::before { content: ""; width: 12px; height: 12px; flex: 0 0 auto; border: 2px solid rgba(167,167,167,0.35); border-top-color: #ddd; border-radius: 50%; animation: status-spin 800ms linear infinite; }
  @keyframes status-spin { to { transform: rotate(360deg); } }
  .status-panel:empty { display: none; }
  .results-panel { margin-top: var(--tool-gap-md); overflow: auto; }
  .results-panel:empty { display: none; }
  .result-card { padding: 10px; word-break: break-word; }
  .data-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
  .data-table th, .data-table td { padding: 8px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--tool-border); }
  .data-table th { color: #a7a7a7; font-size: 11px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase; background: var(--tool-panel); }
  .data-table tr:last-child td { border-bottom: none; }
  .mono { font-family: Consolas, "Courier New", monospace; font-size: 12px; }
  .wrap { overflow-wrap: anywhere; word-break: break-word; }
  .file-name { color: #a7a7a7; font-size: 12px; overflow-wrap: anywhere; }
  .drop-zone {
    margin-top: 8px;
    min-height: 44px;
    display: grid;
    justify-items: center;
    gap: 8px;
    padding: 10px 12px;
    border: 1px dashed #444;
    border-radius: var(--tool-radius);
    color: #a7a7a7;
    background: rgba(128,128,128,0.08);
    font-size: 12px;
    text-align: center;
    transition: border-color 120ms ease, background-color 120ms ease, color 120ms ease;
  }
  .drop-zone .file-row { justify-content: center; }
  .drop-zone-text { color: #a7a7a7; font-size: 12px; }
  .drop-zone.is-dragover {
    border-color: #007fd4;
    color: #ddd;
    background: rgba(0,127,212,0.12);
  }
  .selected-list, .selected-files { margin-top: 8px; }
  .remove-selected, .remove-btn { min-width: 28px; width: 28px; padding: 3px 0; color: #f48771; font-size: 16px; line-height: 1; }
  .pager { margin-top: 8px; display: flex; align-items: center; gap: 8px; justify-content: flex-end; }
  .badge { display: inline-flex; align-items: center; min-height: 18px; padding: 1px 6px; border-radius: 999px; border: 1px solid #444; font-size: 11px; line-height: 1.2; white-space: nowrap; }
  .badge.ok { color: #89d185; background: rgba(137,209,133,0.12); }
  .badge.err { color: #f48771; background: rgba(244,135,113,0.14); }
  .badge.warn { color: #ffd580; background: rgba(210,153,34,0.16); }
  .is-hidden { display: none !important; }
  .err { color: #f48771; white-space: pre-wrap; }
  .ok { color: #89d185; }
  .hint { font-size: 12px; opacity: 0.7; }
`

const SharedDropZoneScript = `
function extractDroppedPaths(dataTransfer) {
  const paths = [];
  const add = (value) => {
    if (!value) return;
    const trimmed = String(value).trim();
    if (trimmed && !paths.includes(trimmed)) paths.push(trimmed);
  };
  const addPathText = (text) => {
    const value = String(text || '');
    if (!value) return;
    const uriMatches = value.match(/file:\/\/(?:\/[A-Za-z]:)?[^\\r\\n\\0]+/gi) || [];
    uriMatches.forEach(add);
    const quotedMatches = value.match(/"([^"]+)"|'([^']+)'/g) || [];
    quotedMatches.forEach(match => add(match.replace(/^["']|["']$/g, '')));
    value.split(/[\r\n\0]+/).forEach(line => {
      let rest = line.trim();
      if (!rest || rest.startsWith('#')) return;
      while (rest.length) {
        const driveMatches = [...rest.matchAll(/[A-Za-z]:\\/g)].map(m => m.index).filter(i => typeof i === 'number');
        if (driveMatches.length <= 1) {
          add(rest);
          break;
        }
        const next = driveMatches[1];
        add(rest.slice(0, next).trim());
        rest = rest.slice(next).trim();
      }
    });
  };
  const addLines = (text) => {
    addPathText(text);
  };
  try { addLines(dataTransfer.getData('text/uri-list')); } catch {}
  try { addLines(dataTransfer.getData('text/plain')); } catch {}
  for (const file of Array.from(dataTransfer.files || [])) {
    add(file.path);
  }
  return paths;
}

  async function extractDroppedHtmlFiles(dataTransfer) {
    const files = [];
    const seen = new Set();
    const droppedFiles = Array.from(dataTransfer.files || []);
    const entries = [];
    const handlePromises = [];
    for (const item of Array.from(dataTransfer.items || [])) {
      if (typeof item.getAsFileSystemHandle === 'function') {
        handlePromises.push(item.getAsFileSystemHandle().catch(() => null));
      }
      let entry = typeof item.webkitGetAsEntry === 'function' ? item.webkitGetAsEntry() : null;
      if (!entry && typeof item.getAsEntry === 'function') entry = item.getAsEntry();
      if (entry) entries.push(entry);
    }
    const addFile = async (file, name) => {
      const fileName = name || file.name || 'dropped.html';
      if (!/\.html?$/i.test(fileName)) return;
    const key = fileName + ':' + file.size + ':' + file.lastModified;
    if (seen.has(key)) return;
    seen.add(key);
    files.push({ name: fileName, content: await file.text() });
  };
  const readEntry = async (entry, prefix) => {
    if (!entry) return;
    if (entry.isFile) {
      await new Promise((resolve) => {
        entry.file(async (file) => {
          await addFile(file, (prefix || '') + file.name);
          resolve();
        }, () => resolve());
      });
      return;
    }
    if (entry.isDirectory) {
      const reader = entry.createReader();
      while (true) {
        const entries = await new Promise(resolve => reader.readEntries(resolve, () => resolve([])));
        if (!entries.length) break;
        for (const child of entries) await readEntry(child, (prefix || '') + entry.name + '/');
      }
    }
  };
  const readHandle = async (handle, prefix) => {
    if (!handle) return;
    if (handle.kind === 'file') {
      try {
        const file = await handle.getFile();
        await addFile(file, (prefix || '') + file.name);
      } catch {}
      return;
    }
    if (handle.kind === 'directory') {
      try {
        for await (const child of handle.values()) {
          await readHandle(child, (prefix || '') + handle.name + '/');
        }
      } catch {}
    }
  };
  for (const file of droppedFiles) await addFile(file, file.name);
  for (const entry of entries) await readEntry(entry, '');
  for (const handle of await Promise.all(handlePromises)) await readHandle(handle, '');
  return files;
}

function setupDropZone(id, statusElement, onResolved) {
  const zone = document.getElementById(id);
    if (!zone) return;
    const fallback = 'This view could not read dropped file paths. Use Select File(s) or Select Folder instead.';
  const setMessage = (text) => {
    if (statusElement) statusElement.textContent = text || '';
  };
    ['dragenter', 'dragover'].forEach(type => {
      zone.addEventListener(type, (event) => {
        if (event.target && event.target.closest && event.target.closest('button, input')) return;
        event.preventDefault();
        event.stopPropagation();
        zone.classList.add('is-dragover');
      });
  });
  ['dragleave', 'dragend'].forEach(type => {
    zone.addEventListener(type, (event) => {
      event.preventDefault();
      event.stopPropagation();
      zone.classList.remove('is-dragover');
    });
  });
  zone.addEventListener('drop', async (event) => {
    event.preventDefault();
    event.stopPropagation();
    zone.classList.remove('is-dragover');
    const paths = extractDroppedPaths(event.dataTransfer);
    const files = await extractDroppedHtmlFiles(event.dataTransfer);
    if (!paths.length && !files.length) {
      setMessage(fallback);
      return;
    }
    setMessage('');
    try {
      const r = await fetch('/api/drop/resolve', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ paths, files }),
      });
      const j = await r.json();
      if (typeof onResolved === 'function') onResolved(j);
    } catch (e) {
      setMessage('Could not read dropped items: ' + (e.message || e));
    }
  });
}
`

type navItem struct {
	Path        string
	Label       string
	Description string
	Beta        bool
}

var navItems = []navItem{
	{Path: "/", Label: "Home"},
	{Path: "/plec", Label: "PLEC Upload", Description: "Upload HTML to the internal PLEC server"},
	{Path: "/applovin", Label: "AppLovin Playable Preview", Description: "Upload to p.applov.in (QR preview)"},
	{Path: "/base64", Label: "Base64 Scanner", Description: "Find non-base64 assets in HTML"},
	{Path: "/mobile", Label: "Send To Mobile", Description: "Share HTML to a phone via LAN + QR"},
	{Path: "/mraid", Label: "MRAID Checker", Description: "Check MRAID requirements and best practices"},
	{Path: "/mintegral", Label: "Mintegral Checker", Description: "Validate Mintegral playable ZIPs against PlayTurbo checks"},
	{Path: "/playworks", Label: "Playworks Converter", Description: "Convert Playworks HTML to per-network variants", Beta: true},
}

func visibleNavItems(betaToolsEnabled bool) []navItem {
	out := make([]navItem, 0, len(navItems))
	for _, n := range navItems {
		if n.Beta && !betaToolsEnabled {
			continue
		}
		out = append(out, n)
	}
	return sortNavItemsForDisplay(out)
}

func sortNavItemsForDisplay(items []navItem) []navItem {
	out := append([]navItem(nil), items...)
	for i := 0; i < len(out); i++ {
		for j := i + 1; j < len(out); j++ {
			if out[i].Beta && !out[j].Beta {
				out[i], out[j] = out[j], out[i]
			}
		}
	}
	return out
}

func betaToolCount() int {
	count := 0
	for _, n := range navItems {
		if n.Beta {
			count++
		}
	}
	return count
}

func Page(activePath, title, body string) string {
	cfg := LoadConfig()
	betaToolsEnabled := cfg.BetaToolsEnabled
	var tabs strings.Builder
	for _, n := range visibleNavItems(betaToolsEnabled) {
		cls := ""
		if n.Path == activePath {
			cls = " active"
		}
		label := n.Label
		if n.Beta {
			label += `<span class="beta-pill">Beta</span>`
		}
		tabs.WriteString(`<button type="button" class="nav-link` + cls + `" data-path="` + n.Path + `">` + label + `</button>`)
	}

	betaButtonLabel := "Show beta"
	if betaToolsEnabled {
		betaButtonLabel = "Hide beta"
	}

	return `<!DOCTYPE html>
<html><head>
<meta charset="UTF-8" />
<title>` + title + ` — HPL Toolbox</title>
<style>` + SharedCSS + `</style>
<script>` + SharedDropZoneScript + `</script>
</head><body>
<div class="topbar">` + tabs.String() + `<span class="topbar-spacer"></span></div>
<div class="content">` + body + `</div>
<div class="app-footer">
  <button id="betaToolsToggle" class="beta-tools-toggle" data-enabled="` + boolAttr(betaToolsEnabled) + `">` + betaButtonLabel + `</button>
  <span class="app-footer-version">` + AppVersion + ` - JJGC 00784</span>
  <button type="button" class="app-footer-link" data-path="/changelog">Changelog</button>
</div>
<script>
document.querySelectorAll('[data-path]').forEach((element) => {
  element.addEventListener('click', () => {
    const path = element.dataset.path;
    if (path) window.location.href = path;
  });
});
document.getElementById('betaToolsToggle').addEventListener('click', async (event) => {
  const enabled = event.currentTarget.dataset.enabled === 'true';
  await fetch('/api/betaTools', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ enabled: !enabled }),
  });
  window.location.reload();
});
</script>
</body></html>`
}

func boolAttr(v bool) string {
	if v {
		return "true"
	}
	return "false"
}

func DropResolveEndpoint(w http.ResponseWriter, r *http.Request) {
	type droppedFile struct {
		Name    string `json:"name"`
		Content string `json:"content"`
	}
	var req struct {
		Paths []string      `json:"paths"`
		Files []droppedFile `json:"files"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, map[string]any{"files": []any{}, "paths": []string{}, "skipped": 0, "error": err.Error()})
		return
	}

	seen := map[string]bool{}
	var outPaths []string
	skipped := 0
	addFile := func(p string) {
		if !isHTMLPath(p) {
			skipped++
			return
		}
		key := strings.ToLower(filepath.Clean(p))
		if seen[key] {
			return
		}
		seen[key] = true
		outPaths = append(outPaths, p)
	}

	for _, raw := range req.Paths {
		p := normalizeDroppedPath(raw)
		if p == "" {
			skipped++
			continue
		}
		info, err := os.Stat(p)
		if err != nil {
			skipped++
			continue
		}
		if info.IsDir() {
			for _, filePath := range collectHTMLFiles(p) {
				addFile(filePath)
			}
			continue
		}
		if info.Mode().IsRegular() {
			addFile(p)
			continue
		}
		skipped++
	}

	if len(req.Files) > 0 {
		cleanupStaleStandaloneDropDirs()
		dir, err := os.MkdirTemp("", "hpltoolbox-drop-")
		if err != nil {
			skipped += len(req.Files)
		} else {
			for _, file := range req.Files {
				if !isHTMLPath(file.Name) {
					skipped++
					continue
				}
				name := safeStandaloneDropName(file.Name)
				target := filepath.Join(dir, name)
				ext := filepath.Ext(name)
				base := strings.TrimSuffix(name, ext)
				for i := 1; fileExists(target); i++ {
					target = filepath.Join(dir, fmt.Sprintf("%s_%d%s", base, i, ext))
				}
				if err := os.WriteFile(target, []byte(file.Content), 0644); err != nil {
					skipped++
					continue
				}
				addFile(target)
			}
		}
	}

	files := make([]map[string]string, 0, len(outPaths))
	for _, p := range outPaths {
		files = append(files, map[string]string{"path": p, "name": filepath.Base(p)})
	}
	writeJSON(w, map[string]any{"files": files, "paths": outPaths, "skipped": skipped})
}

func safeStandaloneDropName(name string) string {
	base := filepath.Base(name)
	base = strings.Map(func(r rune) rune {
		switch r {
		case '<', '>', ':', '"', '/', '\\', '|', '?', '*':
			return '_'
		default:
			if r < 32 {
				return '_'
			}
			return r
		}
	}, base)
	base = strings.TrimSpace(base)
	if base == "" {
		return "dropped.html"
	}
	return base
}

func cleanupStaleStandaloneDropDirs() {
	root := os.TempDir()
	entries, err := os.ReadDir(root)
	if err != nil {
		return
	}
	cutoff := time.Now().Add(-24 * time.Hour)
	for _, entry := range entries {
		if !entry.IsDir() || !strings.HasPrefix(entry.Name(), "hpltoolbox-drop-") {
			continue
		}
		dir := filepath.Join(root, entry.Name())
		info, err := entry.Info()
		if err == nil && info.ModTime().Before(cutoff) {
			_ = os.RemoveAll(dir)
		}
	}
}

func normalizeDroppedPath(value string) string {
	result := strings.Trim(strings.TrimSpace(value), `"'`)
	if strings.HasPrefix(strings.ToLower(result), "file://") {
		result = strings.TrimPrefix(result, "file://")
		if decoded, err := url.PathUnescape(result); err == nil {
			result = decoded
		}
		if len(result) >= 3 && result[0] == '/' && result[2] == ':' {
			result = result[1:]
		}
		result = filepath.FromSlash(result)
	}
	return result
}

func isHTMLPath(p string) bool {
	ext := strings.ToLower(filepath.Ext(p))
	return ext == ".html" || ext == ".htm"
}

func BetaToolsEndpoint(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Enabled bool `json:"enabled"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, map[string]any{"ok": false, "error": err.Error()})
		return
	}
	cfg := LoadConfig()
	cfg.BetaToolsEnabled = req.Enabled
	if err := SaveConfig(cfg); err != nil {
		writeJSON(w, map[string]any{"ok": false, "error": err.Error()})
		return
	}
	writeJSON(w, map[string]any{"ok": true})
}
