package main

// ===========================================================================
// ODINT TOOLKIT — Module 6
// Full OSINT/DNS/SSL/Tech/WHOIS/Wayback reconnaissance
// ===========================================================================

import (
	"bufio"
	"bytes"
	"context"
	"crypto/md5"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"math"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/gdamore/tcell/v2"
)

// ===========================================================================
// ODINT TYPES
// ===========================================================================

// OdintTechSignature represents a technology fingerprint for detection.
type OdintTechSignature struct {
	Name     string
	Category string
	Patterns []string
	Headers  map[string]string
	Cookies  []string
	Meta     map[string]string
	Scripts  []string
	HTML     []string
}

// OdintConfig holds API keys for optional enrichment (20+ APIs).
type OdintConfig struct {
	ShodanKey         string `json:"shodan_key"`
	HIBPKey           string `json:"hibp_key"`
	GithubToken       string `json:"github_token"`
	CensysAPIID       string `json:"censys_api_id"`
	CensysAPISecret   string `json:"censys_api_secret"`
	VirusTotalKey     string `json:"virustotal_key"`
	SecurityTrailsKey string `json:"securitytrails_key"`
	HunterIOKey       string `json:"hunter_io_key"`
	IPInfoToken       string `json:"ipinfo_token"`
	DeHashedEmail     string `json:"dehashed_email"`
	DeHashedKey       string `json:"dehashed_key"`
	LeakCheckKey      string `json:"leakcheck_key"`
	SnusbaseKey       string `json:"snusbase_key"`
	IntelXKey         string `json:"intelx_key"`
	ZoomEyeKey        string `json:"zoomeye_key"`
	BinaryEdgeKey     string `json:"binaryedge_key"`
	GreyNoiseKey      string `json:"greynoise_key"`
	FOFAEmail         string `json:"fofa_email"`
	FOFAKey           string `json:"fofa_key"`
	MaxDownloadSizeMB int    `json:"max_download_size_mb"`
	UserAgent         string `json:"user_agent"`
}

// Has*Key helper methods for graceful API key checks
func (c OdintConfig) HasShodanKey() bool         { return c.ShodanKey != "" }
func (c OdintConfig) HasHIBPKey() bool            { return c.HIBPKey != "" }
func (c OdintConfig) HasGithubToken() bool        { return c.GithubToken != "" }
func (c OdintConfig) HasCensysKeys() bool         { return c.CensysAPIID != "" && c.CensysAPISecret != "" }
func (c OdintConfig) HasVirusTotalKey() bool      { return c.VirusTotalKey != "" }
func (c OdintConfig) HasSecurityTrailsKey() bool  { return c.SecurityTrailsKey != "" }
func (c OdintConfig) HasHunterIOKey() bool        { return c.HunterIOKey != "" }
func (c OdintConfig) HasIPInfoToken() bool        { return c.IPInfoToken != "" }
func (c OdintConfig) HasDeHashedKeys() bool       { return c.DeHashedEmail != "" && c.DeHashedKey != "" }
func (c OdintConfig) HasLeakCheckKey() bool       { return c.LeakCheckKey != "" }
func (c OdintConfig) HasSnusbaseKey() bool        { return c.SnusbaseKey != "" }
func (c OdintConfig) HasIntelXKey() bool          { return c.IntelXKey != "" }
func (c OdintConfig) HasZoomEyeKey() bool         { return c.ZoomEyeKey != "" }
func (c OdintConfig) HasBinaryEdgeKey() bool      { return c.BinaryEdgeKey != "" }
func (c OdintConfig) HasGreyNoiseKey() bool       { return c.GreyNoiseKey != "" }
func (c OdintConfig) HasFOFAKeys() bool           { return c.FOFAEmail != "" && c.FOFAKey != "" }

// OdintDNSRecord holds a single DNS record.
type OdintDNSRecord struct {
	Type  string
	Value string
}

// OdintDetectedTech holds a detected technology.
type OdintDetectedTech struct {
	Name     string
	Category string
	Source   string
}

// OdintWaybackSnapshot holds a Wayback Machine result.
type OdintWaybackSnapshot struct {
	URL        string
	Timestamp  string
	MimeType   string
	StatusCode string
}

// --- Wave 3-6 types ported from ODINT-Toolkit v4.0.0 ---

// OdintNmapPort holds nmap port scan result.
type OdintNmapPort struct {
	Port     int
	Protocol string
	State    string
	Service  string
	Version  string
	Product  string
	Extra    string
	CVEs     []OdintCVEInfo
}

// OdintCVEInfo holds a CVE vulnerability record.
type OdintCVEInfo struct {
	ID          string
	Severity    string
	Description string
	Score       float64
}

// OdintWPUser holds a WordPress user enumeration result.
type OdintWPUser struct {
	ID           int
	Username     string
	DisplayName  string
	GravatarHash string
	URL          string
}

// OdintWPRESTData holds WordPress REST API enumeration data.
type OdintWPRESTData struct {
	Posts      int
	Pages      int
	Media      int
	Comments   int
	Namespaces []string
	Plugins    []string
	Themes     []string
	Types      []string
	Taxonomies []string
}

// OdintExposedPath holds a discovered sensitive path.
type OdintExposedPath struct {
	Path       string
	StatusCode int
	Type       string
}

// OdintFormInfo holds an HTML form discovery result.
type OdintFormInfo struct {
	Action string
	Method string
	Type   string
	Fields []string
}

// OdintCookieInfo holds detailed cookie information.
type OdintCookieInfo struct {
	Name     string
	Value    string
	Domain   string
	Path     string
	Expires  string
	Secure   bool
	HttpOnly bool
	SameSite string
}

// OdintWhoisResult holds detailed WHOIS data.
type OdintWhoisResult struct {
	Registrar       string
	CreatedDate     string
	UpdatedDate     string
	ExpiryDate      string
	NameServers     []string
	RegistrantName  string
	RegistrantOrg   string
	RegistrantEmail string
	AdminName       string
	AdminEmail      string
	TechName        string
	TechEmail       string
	Status          []string
	RawText         string
}

// OdintShodanResult holds Shodan host intelligence.
type OdintShodanResult struct {
	IP        string
	Ports     []int
	OS        string
	ISP       string
	Org       string
	Country   string
	City      string
	Vulns     []string
	Hostnames []string
	Services  []OdintShodanService
}

// OdintShodanService holds a single Shodan service entry.
type OdintShodanService struct {
	Port    int
	Proto   string
	Product string
	Version string
	Banner  string
}

// OdintHIBPBreach holds a Have I Been Pwned breach record.
type OdintHIBPBreach struct {
	Email      string
	BreachName string
	BreachDate string
	DataClasses []string
	PwnCount   int
}

// OdintGithubFinding holds a GitHub OSINT search result.
type OdintGithubFinding struct {
	Type     string
	Name     string
	URL      string
	Snippet  string
	Language string
}

// OdintSocialProfile holds a social media profile check result.
type OdintSocialProfile struct {
	Platform string
	URL      string
	Username string
	Exists   bool
	Status   int
}

// OdintCloudExposure holds a cloud storage bucket discovery result.
type OdintCloudExposure struct {
	Provider   string
	BucketName string
	URL        string
	Status     int
	Accessible bool
}

// OdintPasteFinding holds a paste site match.
type OdintPasteFinding struct {
	Source string
	URL    string
	Title  string
	Date   string
}

// OdintNetworkIntelResult holds ASN/BGP/geo intelligence.
type OdintNetworkIntelResult struct {
	ASN       string
	ASNOrg    string
	BGPPrefix string
	RIR       string
	Country   string
	City      string
	ISP       string
	Lat       float64
	Lon       float64
}

// OdintJWTToken holds a discovered JWT token with decoded claims.
type OdintJWTToken struct {
	Raw       string
	Header    map[string]interface{}
	Payload   map[string]interface{}
	Claims    map[string]interface{}
	Algorithm string
	Issuer    string
	Subject   string
	Location  string
}

// OdintGraphQLEndpoint holds a discovered GraphQL API endpoint.
type OdintGraphQLEndpoint struct {
	URL            string
	Introspection  bool
	Types          []string
	Mutations      []string
	Subscriptions  []string
	Queries        []string
}

// OdintAPIEndpoint holds a generic API endpoint discovery.
type OdintAPIEndpoint struct {
	URL          string
	Method       string
	ContentType  string
	StatusCode   int
	AuthRequired bool
}

// OdintSecretFinding holds a detected secret/credential.
type OdintSecretFinding struct {
	Type     string
	Value    string
	Location string
	Severity string
	Context  string
}

// OdintHistoricalRecord holds a historical data point.
type OdintHistoricalRecord struct {
	Source string
	Type   string
	Data   string
	Date   string
}

// OdintHashCorrelation holds a hash correlation result.
type OdintHashCorrelation struct {
	Hash     string
	HashType string
	Source   string
	Domain   string
	Identity string
}

// OdintErrorPageResult holds error page fingerprinting data.
type OdintErrorPageResult struct {
	StatusCode  int
	ServerInfo  string
	StackTrace  bool
	InternalIPs []string
	DevNames    []string
	FilePaths   []string
	DBErrors    []string
	Framework   string
}

// OdintCookieFingerprint holds cookie-to-technology matching.
type OdintCookieFingerprint struct {
	Name      string
	Value     string
	Flags     string
	TechMatch string
}

// OdintManagementUI holds a discovered management interface.
type OdintManagementUI struct {
	Type       string
	URL        string
	Version    string
	StatusCode int
}

// OdintSubdomainSource holds a subdomain with its discovery source.
type OdintSubdomainSource struct {
	Subdomain string
	Source    string
	IP        string
	Status    int
}

// OdintAnalyticsID holds a tracking/analytics identifier.
type OdintAnalyticsID struct {
	Platform string
	ID       string
}

// OdintCSPDirective holds a CSP policy directive.
type OdintCSPDirective struct {
	Directive string
	Values    []string
}

// OdintIframeSource holds an embedded iframe/object source.
type OdintIframeSource struct {
	URL        string
	Sandbox    string
	ObjectType string
	IsNested   bool
}

// OdintRobotsTxtIntel holds detailed robots.txt analysis.
type OdintRobotsTxtIntel struct {
	UserAgent   string
	Disallows   []string
	Allows      []string
	CrawlDelay  string
	HiddenPaths []string
}

// OdintVirtualHostInfo holds a co-hosted domain discovery.
type OdintVirtualHostInfo struct {
	Domain    string
	IP        string
	Source    string
	IsDefault bool
	StatusCode int
}

// OdintAMPInfo holds AMP detection data.
type OdintAMPInfo struct {
	HasAMP       bool
	AMPURL       string
	AMPElements  []string
	AMPAnalytics []string
	CacheURL     string
}

// OdintWebhookURL holds a discovered webhook endpoint.
type OdintWebhookURL struct {
	Platform string
	URL      string
	Location string
}

// OdintTimezoneLocaleInfo holds timezone/locale intelligence.
type OdintTimezoneLocaleInfo struct {
	ServerTimezone string
	JSTimezones    []string
	DateFormats    []string
	Locale         string
}

// OdintURLPatternInfo holds URL pattern analysis data.
type OdintURLPatternInfo struct {
	PaginationMax  int
	SequentialIDs  []string
	LocalePatterns []string
	DateURLs       []string
}

// OdintStructuredDataItem holds extracted structured data.
type OdintStructuredDataItem struct {
	Type       string
	ItemType   string
	Properties map[string]string
}

// OdintReportingEndpoint holds a reporting API endpoint.
type OdintReportingEndpoint struct {
	Type   string
	URL    string
	Config string
}

// OdintEmailSecurityDNS holds email security DNS record data.
type OdintEmailSecurityDNS struct {
	MTASTSPolicy string
	BIMIRecord   string
	SMTPTLSRpt   string
	DANERecords  []string
}

// OdintDomainRegistrationInfo holds domain registration intelligence.
type OdintDomainRegistrationInfo struct {
	Age              string
	Registrar        string
	ExpiryDate       string
	PrivacyProtected bool
}

// OdintDiscoveredFile holds a file discovered during scanning.
type OdintDiscoveredFile struct {
	URL      string
	Filename string
	FileType string
	Source   string
}

// --- Nmap XML parsing types ---

type odintNmapRun struct {
	Hosts []odintNmapHost `xml:"host"`
}

type odintNmapHost struct {
	Ports odintNmapPorts `xml:"ports"`
}

type odintNmapPorts struct {
	Port []odintNmapXMLPort `xml:"port"`
}

type odintNmapXMLPort struct {
	Protocol string              `xml:"protocol,attr"`
	PortID   int                 `xml:"portid,attr"`
	State    odintNmapState      `xml:"state"`
	Service  odintNmapXMLService `xml:"service"`
}

type odintNmapState struct {
	State string `xml:"state,attr"`
}

type odintNmapXMLService struct {
	Name      string `xml:"name,attr"`
	Product   string `xml:"product,attr"`
	Version   string `xml:"version,attr"`
	ExtraInfo string `xml:"extrainfo,attr"`
}

// --- NVD API response types ---

type odintNVDResponse struct {
	TotalResults int                 `json:"totalResults"`
	Vulns        []odintNVDVulnEntry `json:"vulnerabilities"`
}

type odintNVDVulnEntry struct {
	CVE odintNVDCVE `json:"cve"`
}

type odintNVDCVE struct {
	ID           string          `json:"id"`
	Descriptions []odintNVDDesc  `json:"descriptions"`
	Metrics      odintNVDMetrics `json:"metrics"`
}

type odintNVDDesc struct {
	Lang  string `json:"lang"`
	Value string `json:"value"`
}

type odintNVDMetrics struct {
	CvssV31 []odintNVDCVSSEntry `json:"cvssMetricV31"`
	CvssV2  []odintNVDCVSSEntry `json:"cvssMetricV2"`
}

type odintNVDCVSSEntry struct {
	CvssData odintNVDCVSSData `json:"cvssData"`
}

type odintNVDCVSSData struct {
	BaseScore    float64 `json:"baseScore"`
	BaseSeverity string  `json:"baseSeverity"`
}

// ===========================================================================
// ODINT CONFIG — load/save from JSON next to executable
// ===========================================================================

func odintConfigPath() string {
	exe, _ := os.Executable()
	return filepath.Join(filepath.Dir(exe), "odint-config.json")
}

func odintLoadConfig() OdintConfig {
	var cfg OdintConfig
	data, err := os.ReadFile(odintConfigPath())
	if err != nil {
		return cfg
	}
	json.Unmarshal(data, &cfg)
	return cfg
}

func odintSaveConfig(cfg OdintConfig) {
	data, _ := json.MarshalIndent(cfg, "", "  ")
	os.WriteFile(odintConfigPath(), data, 0644)
}

// ===========================================================================
// ODINT TECH SIGNATURES — 803+ fingerprints (ported from standalone v4.0.0)
// ===========================================================================

func odintLoadSignatures() []OdintTechSignature {
	return []OdintTechSignature{
		// === CMS (19) ===
		{Name: "WordPress", Category: "CMS", Patterns: []string{"/wp-content/", "/wp-includes/", "wp-json"}, Meta: map[string]string{"generator": "WordPress"}},
		{Name: "Drupal", Category: "CMS", Patterns: []string{"Drupal", "/sites/default/", "/sites/all/"}, Meta: map[string]string{"generator": "Drupal"}},
		{Name: "Joomla", Category: "CMS", Patterns: []string{"/components/com_", "/media/jui/"}, Meta: map[string]string{"generator": "Joomla"}},
		{Name: "Shopify", Category: "CMS", Patterns: []string{"cdn.shopify.com", "shopify.com/s/"}},
		{Name: "Magento", Category: "CMS", Patterns: []string{"/skin/frontend/", "/js/mage/", "Mage.Cookies"}},
		{Name: "Wix", Category: "CMS", Patterns: []string{"wix.com", "wixstatic.com", "X-Wix-"}},
		{Name: "Squarespace", Category: "CMS", Patterns: []string{"squarespace.com", "static.squarespace.com"}},
		{Name: "Ghost", Category: "CMS", Patterns: []string{"ghost.io", "content/themes/"}, Meta: map[string]string{"generator": "Ghost"}},
		{Name: "Webflow", Category: "CMS", Patterns: []string{"webflow.com", "assets.website-files.com"}},
		{Name: "Hugo", Category: "CMS", Meta: map[string]string{"generator": "Hugo"}},
		{Name: "Jekyll", Category: "CMS", Meta: map[string]string{"generator": "Jekyll"}},
		{Name: "Gatsby", Category: "CMS", Patterns: []string{"gatsby", "___gatsby"}},
		{Name: "Contentful", Category: "CMS", Patterns: []string{"contentful.com", "ctfassets.net"}},
		{Name: "Strapi", Category: "CMS", Patterns: []string{"/strapi/", "strapi.io"}},
		{Name: "PrestaShop", Category: "CMS", Patterns: []string{"prestashop", "/modules/", "/themes/default-bootstrap/"}},
		{Name: "OpenCart", Category: "CMS", Patterns: []string{"opencart", "/catalog/view/"}},
		{Name: "TYPO3", Category: "CMS", Patterns: []string{"typo3", "/typo3conf/"}, Meta: map[string]string{"generator": "TYPO3"}},
		{Name: "Concrete5", Category: "CMS", Patterns: []string{"concrete5", "/concrete/"}},
		{Name: "Blogger", Category: "CMS", Patterns: []string{"blogger.com", "blogspot.com"}},
		{Name: "Medium", Category: "CMS", Patterns: []string{"medium.com", "cdn-static-1.medium.com"}},
		// === Server (11) ===
		{Name: "Nginx", Category: "Server", Headers: map[string]string{"server": "nginx"}},
		{Name: "Apache", Category: "Server", Headers: map[string]string{"server": "Apache"}},
		{Name: "IIS", Category: "Server", Headers: map[string]string{"server": "Microsoft-IIS"}},
		{Name: "LiteSpeed", Category: "Server", Headers: map[string]string{"server": "LiteSpeed"}},
		{Name: "Cloudflare", Category: "Server", Headers: map[string]string{"server": "cloudflare"}},
		{Name: "OpenResty", Category: "Server", Headers: map[string]string{"server": "openresty"}},
		{Name: "Caddy", Category: "Server", Headers: map[string]string{"server": "Caddy"}},
		{Name: "Tengine", Category: "Server", Headers: map[string]string{"server": "Tengine"}},
		{Name: "Cowboy", Category: "Server", Headers: map[string]string{"server": "Cowboy"}},
		{Name: "gunicorn", Category: "Server", Headers: map[string]string{"server": "gunicorn"}},
		{Name: "Kestrel", Category: "Server", Headers: map[string]string{"server": "Kestrel"}},
		// === CDN (13) ===
		{Name: "Cloudflare", Category: "CDN", Headers: map[string]string{"cf-ray": "", "cf-cache-status": ""}},
		{Name: "Akamai", Category: "CDN", Headers: map[string]string{"x-akamai-transformed": ""}},
		{Name: "Fastly", Category: "CDN", Headers: map[string]string{"x-fastly-request-id": "", "fastly-restarts": ""}},
		{Name: "Amazon CloudFront", Category: "CDN", Headers: map[string]string{"x-amz-cf-id": "", "x-amz-cf-pop": ""}},
		{Name: "Sucuri", Category: "CDN", Headers: map[string]string{"x-sucuri-id": ""}},
		{Name: "KeyCDN", Category: "CDN", Headers: map[string]string{"x-edge-location": ""}},
		{Name: "StackPath", Category: "CDN", Headers: map[string]string{"x-sp-": ""}},
		{Name: "Imperva", Category: "CDN", Headers: map[string]string{"x-iinfo": ""}},
		{Name: "Varnish", Category: "CDN", Headers: map[string]string{"x-varnish": "", "via": "varnish"}},
		{Name: "Azure CDN", Category: "CDN", Headers: map[string]string{"x-ms-ref": ""}},
		{Name: "Google Cloud CDN", Category: "CDN", Patterns: []string{"ghs.googlehosted.com"}},
		{Name: "Netlify", Category: "CDN", Headers: map[string]string{"x-nf-request-id": "", "netlify": ""}},
		{Name: "Vercel", Category: "CDN", Headers: map[string]string{"x-vercel-id": "", "x-vercel-cache": ""}},
		// === WAF (10) ===
		{Name: "Cloudflare WAF", Category: "WAF", Headers: map[string]string{"cf-ray": ""}},
		{Name: "AWS WAF", Category: "WAF", Headers: map[string]string{"x-amzn-waf-": ""}},
		{Name: "Sucuri WAF", Category: "WAF", Headers: map[string]string{"x-sucuri-id": ""}},
		{Name: "Imperva/Incapsula", Category: "WAF", Headers: map[string]string{"x-iinfo": "", "x-cdn": "Incapsula"}},
		{Name: "Akamai WAF", Category: "WAF", Headers: map[string]string{"akamai-grn": ""}},
		{Name: "F5 BIG-IP", Category: "WAF", Cookies: []string{"BIGipServer", "TS"}},
		{Name: "ModSecurity", Category: "WAF", Headers: map[string]string{"server": "ModSecurity"}},
		{Name: "Barracuda", Category: "WAF", Cookies: []string{"barra_counter_session"}},
		{Name: "Fortinet FortiWeb", Category: "WAF", Cookies: []string{"FORTIWAFSID"}},
		{Name: "DenyAll", Category: "WAF", Cookies: []string{"sessioncookie"}},
		// === JS Libraries/Frameworks (39) ===
		{Name: "jQuery", Category: "JS Library", Patterns: []string{"jquery", "jQuery"}, Scripts: []string{"jquery.min.js", "jquery-"}},
		{Name: "React", Category: "JS Framework", Patterns: []string{"react", "_reactRootContainer", "__REACT_DEVTOOLS"}},
		{Name: "Vue.js", Category: "JS Framework", Patterns: []string{"vue.js", "Vue.", "__VUE__", "v-cloak"}},
		{Name: "Angular", Category: "JS Framework", Patterns: []string{"ng-version", "ng-app", "angular.js", "angular.min.js"}},
		{Name: "Next.js", Category: "JS Framework", Patterns: []string{"_next/", "__NEXT_DATA__", "next.js"}},
		{Name: "Nuxt.js", Category: "JS Framework", Patterns: []string{"_nuxt/", "__NUXT__", "nuxt.js"}},
		{Name: "Svelte", Category: "JS Framework", Patterns: []string{"svelte", "__svelte"}},
		{Name: "Ember.js", Category: "JS Framework", Patterns: []string{"ember.js", "Ember."}},
		{Name: "Backbone.js", Category: "JS Library", Patterns: []string{"backbone.js", "Backbone."}},
		{Name: "Bootstrap", Category: "CSS Framework", Patterns: []string{"bootstrap.min.js", "bootstrap.min.css", "bootstrap.css"}},
		{Name: "Tailwind CSS", Category: "CSS Framework", Patterns: []string{"tailwind", "tw-"}},
		{Name: "Foundation", Category: "CSS Framework", Patterns: []string{"foundation.min.js", "foundation.css"}},
		{Name: "Materialize", Category: "CSS Framework", Patterns: []string{"materialize.min.js", "materialize.css"}},
		{Name: "Bulma", Category: "CSS Framework", Patterns: []string{"bulma.min.css", "bulma.css"}},
		{Name: "Lodash", Category: "JS Library", Patterns: []string{"lodash.min.js", "lodash.js"}},
		{Name: "Moment.js", Category: "JS Library", Patterns: []string{"moment.min.js", "moment.js"}},
		{Name: "Axios", Category: "JS Library", Patterns: []string{"axios.min.js", "axios"}},
		{Name: "D3.js", Category: "JS Library", Patterns: []string{"d3.min.js", "d3.js"}},
		{Name: "Three.js", Category: "JS Library", Patterns: []string{"three.min.js", "THREE."}},
		{Name: "GSAP", Category: "JS Library", Patterns: []string{"gsap.min.js", "TweenMax", "TweenLite"}},
		{Name: "Anime.js", Category: "JS Library", Patterns: []string{"anime.min.js"}},
		{Name: "Socket.io", Category: "JS Library", Patterns: []string{"socket.io.js", "socket.io.min.js"}},
		{Name: "Modernizr", Category: "JS Library", Patterns: []string{"modernizr", "Modernizr"}},
		{Name: "Underscore.js", Category: "JS Library", Patterns: []string{"underscore.js", "underscore.min.js"}},
		{Name: "Handlebars", Category: "JS Library", Patterns: []string{"handlebars.js", "Handlebars"}},
		{Name: "Alpine.js", Category: "JS Framework", Patterns: []string{"alpine.js", "x-data", "x-bind"}},
		{Name: "htmx", Category: "JS Library", Patterns: []string{"htmx.min.js", "hx-get", "hx-post"}},
		{Name: "Stimulus", Category: "JS Framework", Patterns: []string{"stimulus.js", "data-controller"}},
		{Name: "Turbo", Category: "JS Library", Patterns: []string{"turbo.js", "data-turbo"}},
		{Name: "Lit", Category: "JS Framework", Patterns: []string{"lit-html", "lit-element"}},
		{Name: "Preact", Category: "JS Framework", Patterns: []string{"preact.min.js", "preact"}},
		{Name: "Inferno", Category: "JS Framework", Patterns: []string{"inferno.min.js"}},
		{Name: "Mithril", Category: "JS Framework", Patterns: []string{"mithril.min.js"}},
		{Name: "Riot.js", Category: "JS Framework", Patterns: []string{"riot.min.js"}},
		{Name: "Polymer", Category: "JS Framework", Patterns: []string{"polymer.html", "@polymer"}},
		{Name: "Stencil", Category: "JS Framework", Patterns: []string{"stencil.js"}},
		{Name: "SolidJS", Category: "JS Framework", Patterns: []string{"solid-js"}},
		{Name: "Qwik", Category: "JS Framework", Patterns: []string{"qwik", "q:container"}},
		{Name: "Astro", Category: "JS Framework", Patterns: []string{"astro", "_astro/"}},
		// === Backend Frameworks (22) ===
		{Name: "Laravel", Category: "Framework", Cookies: []string{"laravel_session", "XSRF-TOKEN"}},
		{Name: "Django", Category: "Framework", Headers: map[string]string{"x-frame-options": "SAMEORIGIN"}, Cookies: []string{"csrftoken", "django"}},
		{Name: "Ruby on Rails", Category: "Framework", Headers: map[string]string{"x-runtime": "", "x-request-id": ""}, Cookies: []string{"_session_id"}},
		{Name: "Express.js", Category: "Framework", Headers: map[string]string{"x-powered-by": "Express"}},
		{Name: "ASP.NET", Category: "Framework", Headers: map[string]string{"x-aspnet-version": "", "x-powered-by": "ASP.NET"}, Cookies: []string{"ASP.NET_SessionId", ".AspNetCore."}},
		{Name: "Spring", Category: "Framework", Headers: map[string]string{"x-application-context": ""}, Cookies: []string{"JSESSIONID"}},
		{Name: "Flask", Category: "Framework", Headers: map[string]string{"server": "Werkzeug"}},
		{Name: "FastAPI", Category: "Framework", Patterns: []string{"fastapi", "/docs", "/openapi.json"}},
		{Name: "Phoenix", Category: "Framework", Patterns: []string{"phoenix"}, Cookies: []string{"_csrf_token"}},
		{Name: "Symfony", Category: "Framework", Cookies: []string{"symfony"}},
		{Name: "CodeIgniter", Category: "Framework", Cookies: []string{"ci_session"}},
		{Name: "CakePHP", Category: "Framework", Cookies: []string{"cakephp", "CAKEPHP"}},
		{Name: "Yii", Category: "Framework", Cookies: []string{"PHPSESSID", "YII_CSRF_TOKEN"}},
		{Name: "Slim", Category: "Framework", Patterns: []string{"Slim\\\\"}},
		{Name: "Koa", Category: "Framework", Headers: map[string]string{"x-powered-by": "koa"}},
		{Name: "Hapi", Category: "Framework", Patterns: []string{"hapi"}},
		{Name: "NestJS", Category: "Framework", Patterns: []string{"nestjs"}},
		{Name: "Gin", Category: "Framework", Patterns: []string{"gin-gonic"}},
		{Name: "Echo", Category: "Framework", Patterns: []string{"labstack/echo"}},
		{Name: "Fiber", Category: "Framework", Patterns: []string{"gofiber"}},
		{Name: "Actix", Category: "Framework", Patterns: []string{"actix"}},
		{Name: "Rocket", Category: "Framework", Patterns: []string{"rocket"}},
		// === Programming Languages (9) ===
		{Name: "PHP", Category: "Language", Patterns: []string{".php"}, Headers: map[string]string{"x-powered-by": "PHP"}},
		{Name: "Python", Category: "Language", Headers: map[string]string{"server": "Python"}},
		{Name: "Node.js", Category: "Language", Headers: map[string]string{"x-powered-by": "Express"}},
		{Name: "Java", Category: "Language", Cookies: []string{"JSESSIONID"}},
		{Name: "Ruby", Category: "Language", Headers: map[string]string{"x-runtime": ""}},
		{Name: "Go", Category: "Language", Patterns: []string{"Go-http-client"}},
		{Name: "Rust", Category: "Language", Patterns: []string{"actix", "rocket"}},
		{Name: "Elixir", Category: "Language", Patterns: []string{"phoenix", "elixir"}},
		{Name: ".NET", Category: "Language", Headers: map[string]string{"x-aspnet-version": ""}},
		// === Analytics (24) ===
		{Name: "Google Analytics", Category: "Analytics", Patterns: []string{"google-analytics.com", "googletagmanager.com", "gtag", "ga.js", "analytics.js"}},
		{Name: "Google Tag Manager", Category: "Analytics", Patterns: []string{"googletagmanager.com/gtm.js"}},
		{Name: "Facebook Pixel", Category: "Analytics", Patterns: []string{"facebook.com/tr", "fbq("}},
		{Name: "Hotjar", Category: "Analytics", Patterns: []string{"hotjar.com", "hj.js"}},
		{Name: "Mixpanel", Category: "Analytics", Patterns: []string{"mixpanel.com", "mixpanel"}},
		{Name: "Segment", Category: "Analytics", Patterns: []string{"segment.com", "analytics.js"}},
		{Name: "Amplitude", Category: "Analytics", Patterns: []string{"amplitude.com", "amplitude"}},
		{Name: "Heap", Category: "Analytics", Patterns: []string{"heap.io", "heapanalytics.com"}},
		{Name: "Plausible", Category: "Analytics", Patterns: []string{"plausible.io"}},
		{Name: "Matomo", Category: "Analytics", Patterns: []string{"matomo", "piwik"}},
		{Name: "Clicky", Category: "Analytics", Patterns: []string{"clicky.com", "static.getclicky.com"}},
		{Name: "New Relic", Category: "Analytics", Patterns: []string{"newrelic.com", "nr-data.net"}},
		{Name: "Datadog", Category: "Analytics", Patterns: []string{"datadoghq.com"}},
		{Name: "Sentry", Category: "Analytics", Patterns: []string{"sentry.io", "@sentry"}},
		{Name: "FullStory", Category: "Analytics", Patterns: []string{"fullstory.com"}},
		{Name: "LogRocket", Category: "Analytics", Patterns: []string{"logrocket.com"}},
		{Name: "Clarity", Category: "Analytics", Patterns: []string{"clarity.ms"}},
		{Name: "Hubspot", Category: "Analytics", Patterns: []string{"hubspot.com", "hs-scripts.com"}},
		{Name: "Intercom", Category: "Analytics", Patterns: []string{"intercom.io", "intercomcdn.com"}},
		{Name: "Drift", Category: "Analytics", Patterns: []string{"drift.com", "js.driftt.com"}},
		{Name: "Crisp", Category: "Analytics", Patterns: []string{"crisp.chat"}},
		{Name: "Zendesk", Category: "Analytics", Patterns: []string{"zendesk.com", "zdassets.com"}},
		{Name: "Tawk.to", Category: "Analytics", Patterns: []string{"tawk.to"}},
		{Name: "LiveChat", Category: "Analytics", Patterns: []string{"livechatinc.com"}},
		// === Hosting/Cloud (20) ===
		{Name: "AWS", Category: "Hosting", Patterns: []string{"amazonaws.com", "aws.amazon.com"}, Headers: map[string]string{"x-amz-": ""}},
		{Name: "Google Cloud", Category: "Hosting", Patterns: []string{"googleapis.com", "googleusercontent.com", "appspot.com"}},
		{Name: "Azure", Category: "Hosting", Patterns: []string{"azure.com", "azurewebsites.net", "windows.net"}},
		{Name: "Heroku", Category: "Hosting", Patterns: []string{"herokuapp.com"}, Headers: map[string]string{"via": "heroku"}},
		{Name: "DigitalOcean", Category: "Hosting", Patterns: []string{"digitalocean.com", "ondigitalocean.app"}},
		{Name: "Netlify", Category: "Hosting", Patterns: []string{"netlify.app", "netlify.com"}, Headers: map[string]string{"x-nf-request-id": ""}},
		{Name: "Vercel", Category: "Hosting", Patterns: []string{"vercel.app", "now.sh"}, Headers: map[string]string{"x-vercel-id": ""}},
		{Name: "Firebase", Category: "Hosting", Patterns: []string{"firebase.com", "firebaseapp.com", "firebaseio.com"}},
		{Name: "Render", Category: "Hosting", Patterns: []string{"onrender.com"}},
		{Name: "Railway", Category: "Hosting", Patterns: []string{"railway.app"}},
		{Name: "Fly.io", Category: "Hosting", Patterns: []string{"fly.io", "fly.dev"}},
		{Name: "Supabase", Category: "Hosting", Patterns: []string{"supabase.co", "supabase.io"}},
		{Name: "PlanetScale", Category: "Hosting", Patterns: []string{"planetscale.com"}},
		{Name: "Linode", Category: "Hosting", Patterns: []string{"linode.com"}},
		{Name: "Vultr", Category: "Hosting", Patterns: []string{"vultr.com"}},
		{Name: "OVH", Category: "Hosting", Patterns: []string{"ovh.com", "ovh.net"}},
		{Name: "Hetzner", Category: "Hosting", Patterns: []string{"hetzner.com"}},
		{Name: "Cloudflare Pages", Category: "Hosting", Patterns: []string{"pages.dev"}},
		{Name: "GitHub Pages", Category: "Hosting", Patterns: []string{"github.io"}},
		{Name: "GitLab Pages", Category: "Hosting", Patterns: []string{"gitlab.io"}},
		// === E-commerce (10) ===
		{Name: "Shopify", Category: "E-commerce", Patterns: []string{"cdn.shopify.com", "myshopify.com"}},
		{Name: "WooCommerce", Category: "E-commerce", Patterns: []string{"woocommerce", "/wp-content/plugins/woocommerce/"}},
		{Name: "Magento", Category: "E-commerce", Patterns: []string{"/skin/frontend/", "Mage.Cookies"}},
		{Name: "BigCommerce", Category: "E-commerce", Patterns: []string{"bigcommerce.com", "mybigcommerce.com"}},
		{Name: "PrestaShop", Category: "E-commerce", Patterns: []string{"prestashop", "/modules/"}},
		{Name: "OpenCart", Category: "E-commerce", Patterns: []string{"/catalog/view/"}},
		{Name: "Stripe", Category: "Payment", Patterns: []string{"stripe.com", "js.stripe.com"}},
		{Name: "PayPal", Category: "Payment", Patterns: []string{"paypal.com", "paypalobjects.com"}},
		{Name: "Square", Category: "Payment", Patterns: []string{"squareup.com", "square.com"}},
		{Name: "Braintree", Category: "Payment", Patterns: []string{"braintreegateway.com"}},
		// === Security (8) ===
		{Name: "reCAPTCHA", Category: "Security", Patterns: []string{"google.com/recaptcha", "recaptcha.net"}},
		{Name: "hCaptcha", Category: "Security", Patterns: []string{"hcaptcha.com"}},
		{Name: "Turnstile", Category: "Security", Patterns: []string{"challenges.cloudflare.com/turnstile"}},
		{Name: "Auth0", Category: "Security", Patterns: []string{"auth0.com", "cdn.auth0.com"}},
		{Name: "Okta", Category: "Security", Patterns: []string{"okta.com"}},
		{Name: "Firebase Auth", Category: "Security", Patterns: []string{"firebaseauth", "identitytoolkit.googleapis.com"}},
		{Name: "Clerk", Category: "Security", Patterns: []string{"clerk.dev", "clerk.com"}},
		{Name: "Supabase Auth", Category: "Security", Patterns: []string{"supabase.co/auth"}},
		// === Reverse Proxy (19) ===
		{Name: "HAProxy", Category: "Reverse Proxy", Patterns: []string{"haproxy"}, Headers: map[string]string{"x-haproxy-server-state": ""}},
		{Name: "HAProxy (Stats)", Category: "Reverse Proxy", Patterns: []string{"/haproxy?stats", "HAProxy Statistics"}},
		{Name: "Envoy Proxy", Category: "Reverse Proxy", Patterns: []string{"envoy"}, Headers: map[string]string{"x-envoy-upstream-service-time": ""}},
		{Name: "Envoy (Decorator)", Category: "Reverse Proxy", Headers: map[string]string{"x-envoy-decorator-operation": ""}},
		{Name: "Traefik", Category: "Reverse Proxy", Patterns: []string{"traefik"}, Headers: map[string]string{"x-traefik-": ""}},
		{Name: "F5 BIG-IP", Category: "Reverse Proxy", Headers: map[string]string{"x-cnection": ""}, Cookies: []string{"BIGipServer"}},
		{Name: "F5 BIG-IP (Persistence)", Category: "Reverse Proxy", Cookies: []string{"BIGipServerPool"}},
		{Name: "AWS ALB", Category: "Reverse Proxy", Headers: map[string]string{"x-amzn-trace-id": ""}},
		{Name: "AWS ELB", Category: "Reverse Proxy", Cookies: []string{"AWSELB", "AWSELBID"}},
		{Name: "Azure Application Gateway", Category: "Reverse Proxy", Headers: map[string]string{"x-ms-request-id": ""}, Cookies: []string{"ApplicationGatewayAffinity"}},
		{Name: "Nginx Plus", Category: "Reverse Proxy", Headers: map[string]string{"server": "nginx"}, Cookies: []string{"srv_id"}},
		{Name: "Pound", Category: "Reverse Proxy", Headers: map[string]string{"server": "Pound"}},
		{Name: "Squid", Category: "Reverse Proxy", Headers: map[string]string{"x-squid-error": "", "via": "squid"}},
		{Name: "Varnish Proxy", Category: "Reverse Proxy", Headers: map[string]string{"x-varnish": "", "via": "varnish"}},
		{Name: "Apache Traffic Server", Category: "Reverse Proxy", Patterns: []string{"Apache Traffic Server"}, Headers: map[string]string{"server": "ATS"}},
		{Name: "Caddy Reverse Proxy", Category: "Reverse Proxy", Patterns: []string{"caddy"}, Headers: map[string]string{"server": "Caddy"}},
		{Name: "Relayd (OpenBSD)", Category: "Reverse Proxy", Patterns: []string{"relayd"}},
		{Name: "Skipper (Zalando)", Category: "Reverse Proxy", Patterns: []string{"skipper"}, Headers: map[string]string{"x-skipper-": ""}},
		{Name: "YARP (.NET)", Category: "Reverse Proxy", Patterns: []string{"yarp"}, Headers: map[string]string{"x-yarp-": ""}},
		// === API Gateway (11) ===
		{Name: "Kong Gateway", Category: "API Gateway", Headers: map[string]string{"x-kong-upstream-latency": "", "x-kong-proxy-latency": ""}},
		{Name: "AWS API Gateway", Category: "API Gateway", Headers: map[string]string{"x-amzn-requestid": "", "x-amz-apigw-id": ""}},
		{Name: "Azure API Management", Category: "API Gateway", Patterns: []string{"azure-api.net"}, Headers: map[string]string{"ocp-apim-trace-location": ""}},
		{Name: "Apigee", Category: "API Gateway", Patterns: []string{"apigee.net"}, Headers: map[string]string{"x-apigee-proxy": ""}},
		{Name: "Tyk Gateway", Category: "API Gateway", Headers: map[string]string{"x-tyk-authorization": "", "x-ratelimit-limit": ""}},
		{Name: "MuleSoft Anypoint", Category: "API Gateway", Patterns: []string{"mulesoft.com", "anypoint.mulesoft"}, Headers: map[string]string{"x-mule-": ""}},
		{Name: "Gravitee.io", Category: "API Gateway", Headers: map[string]string{"x-gravitee-transaction-id": "", "x-gravitee-request-id": ""}},
		{Name: "Express Gateway", Category: "API Gateway", Headers: map[string]string{"x-powered-by": "Express Gateway"}},
		{Name: "KrakenD", Category: "API Gateway", Patterns: []string{"krakend"}, Headers: map[string]string{"x-krakend": ""}},
		{Name: "Zuul (Netflix)", Category: "API Gateway", Patterns: []string{"zuul"}, Headers: map[string]string{"x-zuul-": ""}},
		{Name: "Ambassador (Envoy)", Category: "API Gateway", Patterns: []string{"ambassador"}, Headers: map[string]string{"x-envoy-upstream-service-time": ""}},
		// === Control Panel (11) ===
		{Name: "cPanel", Category: "Control Panel", Patterns: []string{":2083", ":2082", "cpanel", "/cPanel"}, Cookies: []string{"cprelogin", "cpsession"}},
		{Name: "Plesk", Category: "Control Panel", Patterns: []string{":8443/login", "plesk", "/modules/plesk/"}, Headers: map[string]string{"x-powered-by": "PleskLin"}},
		{Name: "DirectAdmin", Category: "Control Panel", Patterns: []string{":2222", "directadmin", "/CMD_LOGIN"}},
		{Name: "Webmin", Category: "Control Panel", Patterns: []string{":10000", "webmin"}, Cookies: []string{"sid", "testing"}},
		{Name: "CyberPanel", Category: "Control Panel", Patterns: []string{":8090", "cyberpanel"}},
		{Name: "VestaCP", Category: "Control Panel", Patterns: []string{":8083", "vestacp"}},
		{Name: "ISPConfig", Category: "Control Panel", Patterns: []string{":8080/login", "ispconfig"}},
		{Name: "Virtualmin", Category: "Control Panel", Patterns: []string{":10000/virtual-server/", "virtualmin"}},
		{Name: "HestiaCP", Category: "Control Panel", Patterns: []string{":8083", "hestiacp"}},
		{Name: "CloudPanel", Category: "Control Panel", Patterns: []string{":8443", "cloudpanel"}},
		{Name: "aaPanel", Category: "Control Panel", Patterns: []string{":8888", "aapanel"}},
		// === Serverless (8) ===
		{Name: "AWS Lambda", Category: "Serverless", Headers: map[string]string{"x-amzn-remapped-content-length": "", "x-amzn-requestid": ""}},
		{Name: "AWS Lambda@Edge", Category: "Serverless", Patterns: []string{"lambda@edge"}, Headers: map[string]string{"x-amz-cf-id": ""}},
		{Name: "Azure Functions", Category: "Serverless", Patterns: []string{"azurewebsites.net/api/", ".azurewebsites.net"}, Headers: map[string]string{"x-azure-ref": ""}},
		{Name: "Cloudflare Workers", Category: "Serverless", Patterns: []string{"workers.dev"}, Headers: map[string]string{"cf-ray": ""}},
		{Name: "Vercel Edge Functions", Category: "Serverless", Headers: map[string]string{"x-vercel-id": "", "x-matched-path": ""}},
		{Name: "Netlify Functions", Category: "Serverless", Patterns: []string{"/.netlify/functions/"}, Headers: map[string]string{"x-nf-request-id": ""}},
		{Name: "Google Cloud Functions", Category: "Serverless", Patterns: []string{"cloudfunctions.net"}, Headers: map[string]string{"function-execution-id": ""}},
		{Name: "Deno Deploy", Category: "Serverless", Patterns: []string{"deno.dev"}, Headers: map[string]string{"server": "deno"}},
		// === Container (11) ===
		{Name: "Docker", Category: "Container", Patterns: []string{"docker", "Docker-Distribution-Api-Version"}, Headers: map[string]string{"x-docker-": ""}},
		{Name: "Kubernetes", Category: "Container", Patterns: []string{"kubernetes", "k8s"}, Headers: map[string]string{"x-kubernetes-pf-flowschema-uid": ""}},
		{Name: "AWS ECS", Category: "Container", Patterns: []string{"ecs.amazonaws.com"}, Headers: map[string]string{"x-amzn-ecs-": ""}},
		{Name: "AWS EKS", Category: "Container", Patterns: []string{"eks.amazonaws.com"}},
		{Name: "Google GKE", Category: "Container", Patterns: []string{"container.googleapis.com"}, Headers: map[string]string{"x-goog-": ""}},
		{Name: "Azure AKS", Category: "Container", Patterns: []string{"azmk8s.io", "aksapp.io"}},
		{Name: "HashiCorp Nomad", Category: "Container", Patterns: []string{"nomad"}, Headers: map[string]string{"x-nomad-": ""}},
		{Name: "Rancher", Category: "Container", Patterns: []string{"rancher", "/v3/", "/dashboard/"}, Cookies: []string{"R_SESS"}},
		{Name: "Docker Swarm", Category: "Container", Patterns: []string{"docker-swarm", "swarm"}},
		{Name: "OpenShift", Category: "Container", Patterns: []string{"openshift", "apps.openshift.io"}, Cookies: []string{"openshift-session-token"}},
		{Name: "Podman", Category: "Container", Patterns: []string{"podman"}},
		// === Protocols (9) ===
		{Name: "HTTP/3 (QUIC)", Category: "Protocol", Headers: map[string]string{"alt-svc": "h3"}},
		{Name: "WebSocket", Category: "Protocol", Patterns: []string{"ws://", "wss://", "WebSocket"}, Headers: map[string]string{"upgrade": "websocket"}},
		{Name: "gRPC-Web", Category: "Protocol", Patterns: []string{"grpc-web"}, Headers: map[string]string{"content-type": "application/grpc-web"}},
		{Name: "Server-Sent Events", Category: "Protocol", Patterns: []string{"EventSource", "text/event-stream"}, Headers: map[string]string{"content-type": "text/event-stream"}},
		{Name: "SPDY", Category: "Protocol", Headers: map[string]string{"alt-svc": "spdy"}},
		{Name: "HTTP/2 Server Push", Category: "Protocol", Patterns: []string{"push"}, Headers: map[string]string{"link": "preload"}},
		{Name: "GraphQL", Category: "Protocol", Patterns: []string{"/graphql", "graphiql", "__schema"}},
		{Name: "JSON-RPC", Category: "Protocol", Patterns: []string{"jsonrpc", "json-rpc"}, Headers: map[string]string{"content-type": "application/json-rpc"}},
		{Name: "MQTT over WebSocket", Category: "Protocol", Patterns: []string{"mqtt", "mqttws"}},
		// === CMS Extended (8) ===
		{Name: "Sitecore", Category: "CMS", Patterns: []string{"/sitecore/", "sitecore"}, Cookies: []string{"SC_ANALYTICS_GLOBAL_COOKIE", "sitecore_userticket"}, Meta: map[string]string{"generator": "Sitecore"}},
		{Name: "Umbraco", Category: "CMS", Patterns: []string{"/umbraco/", "umbraco"}, Meta: map[string]string{"generator": "Umbraco"}},
		{Name: "Adobe Experience Manager", Category: "CMS", Patterns: []string{"/content/dam/", "/etc.clientlibs/", "/etc/designs/"}, Headers: map[string]string{"x-dispatcher-info": ""}},
		{Name: "Kentico", Category: "CMS", Patterns: []string{"/Kentico", "/CMSPages/"}, Cookies: []string{"CMSPreferredCulture"}, Meta: map[string]string{"generator": "Kentico"}},
		{Name: "DNN (DotNetNuke)", Category: "CMS", Patterns: []string{"/DesktopModules/", "/Portals/", "DotNetNuke"}, Cookies: []string{".DOTNETNUKE", "dnn_IsMobile"}},
		{Name: "Craft CMS", Category: "CMS", Patterns: []string{"/cpresources/", "craftcms"}, Cookies: []string{"CraftSessionId"}, Meta: map[string]string{"generator": "Craft CMS"}},
		{Name: "Statamic", Category: "CMS", Patterns: []string{"statamic", "/cp/"}, Meta: map[string]string{"generator": "Statamic"}},
		{Name: "ExpressionEngine", Category: "CMS", Patterns: []string{"ExpressionEngine", "/themes/ee/"}, Cookies: []string{"exp_tracker", "exp_csrf_token"}},
		// === WordPress Ecosystem (20) ===
		{Name: "Elementor", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/elementor/", "elementor-widget"}, Scripts: []string{"elementor/assets/js/"}},
		{Name: "Divi", Category: "WordPress Plugin", Patterns: []string{"/wp-content/themes/Divi/", "et-boc", "et_pb_"}, Scripts: []string{"/Divi/js/"}},
		{Name: "Yoast SEO", Category: "WordPress Plugin", Patterns: []string{"yoast-schema-graph", "yoast.com/wordpress/plugins/seo"}, HTML: []string{"<!-- This site is optimized with the Yoast SEO plugin"}},
		{Name: "WP Rocket", Category: "WordPress Plugin", Patterns: []string{"wp-rocket", "/wp-content/cache/wp-rocket/"}, HTML: []string{"<!-- This website is like a Rocket"}},
		{Name: "Wordfence", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/wordfence/", "wordfence"}, HTML: []string{"<!-- Wordfence"}},
		{Name: "WooCommerce Extended", Category: "WordPress Plugin", Patterns: []string{"wc-ajax", "woocommerce-product", "/wp-content/plugins/woocommerce/"}, Scripts: []string{"woocommerce/assets/js/"}},
		{Name: "Contact Form 7", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/contact-form-7/", "wpcf7"}, Scripts: []string{"contact-form-7/includes/js/"}},
		{Name: "Jetpack", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/jetpack/", "jetpack"}, Scripts: []string{"jetpack/"}},
		{Name: "WPML", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/sitepress-multilingual-cms/", "wpml-config"}, Meta: map[string]string{"generator": "WPML"}},
		{Name: "Gravity Forms", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/gravityforms/", "gform_wrapper"}, Scripts: []string{"gravityforms/js/"}},
		{Name: "ACF (Advanced Custom Fields)", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/advanced-custom-fields/", "acf-"}},
		{Name: "WP Super Cache", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/wp-super-cache/"}, HTML: []string{"<!-- Dynamic page generated in", "<!-- super cache"}},
		{Name: "W3 Total Cache (WP)", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/w3-total-cache/"}, HTML: []string{"<!-- W3 Total Cache"}},
		{Name: "Beaver Builder", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/beaver-builder-lite-version/", "fl-builder"}, Scripts: []string{"fl-builder/js/"}},
		{Name: "Rank Math", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/seo-by-rank-math/", "rank-math"}, HTML: []string{"<!-- Rank Math"}},
		{Name: "All in One SEO", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/all-in-one-seo-pack/", "aioseo"}, HTML: []string{"<!-- All in One SEO"}},
		{Name: "WPForms", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/wpforms-lite/", "wpforms"}, Scripts: []string{"wpforms/assets/js/"}},
		{Name: "MonsterInsights", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/google-analytics-for-wordpress/", "monsterinsights"}, HTML: []string{"<!-- MonsterInsights"}},
		{Name: "UpdraftPlus", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/updraftplus/"}},
		{Name: "Sucuri Security WP", Category: "WordPress Plugin", Patterns: []string{"/wp-content/plugins/sucuri-scanner/"}, HTML: []string{"<!-- Sucuri"}},
		// === PHP Frameworks (9) ===
		{Name: "CodeIgniter", Category: "PHP Framework", Patterns: []string{"codeigniter"}, Cookies: []string{"ci_session"}},
		{Name: "CakePHP", Category: "PHP Framework", Patterns: []string{"cakephp"}, Cookies: []string{"cakephp", "CAKEPHP"}},
		{Name: "Yii Framework", Category: "PHP Framework", Patterns: []string{"yii", "Yii::"}, Headers: map[string]string{"x-powered-by": "Yii"}, Cookies: []string{"YII_CSRF_TOKEN"}},
		{Name: "Slim Framework", Category: "PHP Framework", Patterns: []string{"Slim\\\\", "slim/slim"}},
		{Name: "Lumen", Category: "PHP Framework", Patterns: []string{"lumen"}, Headers: map[string]string{"x-powered-by": "Lumen"}},
		{Name: "Phalcon", Category: "PHP Framework", Headers: map[string]string{"x-powered-by": "Phalcon"}, Cookies: []string{"phalcon"}},
		{Name: "FuelPHP", Category: "PHP Framework", Patterns: []string{"fuelphp"}, Cookies: []string{"fuelcid"}},
		{Name: "Zend / Laminas", Category: "PHP Framework", Patterns: []string{"zendframework", "laminas"}},
		{Name: "Nette", Category: "PHP Framework", Patterns: []string{"nette"}, Cookies: []string{"nette-browser"}},
		// === Python Frameworks (9) ===
		{Name: "Tornado", Category: "Python Framework", Headers: map[string]string{"server": "TornadoServer"}},
		{Name: "Pyramid", Category: "Python Framework", Patterns: []string{"pyramid"}, Headers: map[string]string{"x-powered-by": "Pyramid"}},
		{Name: "Starlette", Category: "Python Framework", Patterns: []string{"starlette"}},
		{Name: "Bottle", Category: "Python Framework", Patterns: []string{"bottle"}, Headers: map[string]string{"server": "WSGIServer"}},
		{Name: "Falcon", Category: "Python Framework", Patterns: []string{"falcon"}},
		{Name: "Sanic", Category: "Python Framework", Headers: map[string]string{"server": "sanic"}},
		{Name: "aiohttp", Category: "Python Framework", Headers: map[string]string{"server": "aiohttp"}},
		{Name: "Quart", Category: "Python Framework", Patterns: []string{"quart"}},
		{Name: "Litestar", Category: "Python Framework", Patterns: []string{"litestar"}},
		// === Static Site Generators (11) ===
		{Name: "Eleventy", Category: "Static Site Generator", Patterns: []string{"eleventy"}, Meta: map[string]string{"generator": "Eleventy"}},
		{Name: "Astro (SSG)", Category: "Static Site Generator", Patterns: []string{"_astro/", "astro-island"}, Meta: map[string]string{"generator": "Astro"}},
		{Name: "Docusaurus", Category: "Static Site Generator", Patterns: []string{"docusaurus", "__docusaurus"}, Meta: map[string]string{"generator": "Docusaurus"}},
		{Name: "MkDocs", Category: "Static Site Generator", Patterns: []string{"mkdocs", "/mkdocs/"}, Meta: map[string]string{"generator": "mkdocs"}},
		{Name: "VitePress", Category: "Static Site Generator", Patterns: []string{"vitepress", ".vitepress/"}, Meta: map[string]string{"generator": "VitePress"}},
		{Name: "Pelican", Category: "Static Site Generator", Patterns: []string{"pelican"}, Meta: map[string]string{"generator": "Pelican"}},
		{Name: "Hexo", Category: "Static Site Generator", Patterns: []string{"hexo"}, Meta: map[string]string{"generator": "Hexo"}},
		{Name: "Zola", Category: "Static Site Generator", Patterns: []string{"zola"}, Meta: map[string]string{"generator": "Zola"}},
		{Name: "Gridsome", Category: "Static Site Generator", Patterns: []string{"gridsome", "__gridsome"}, Meta: map[string]string{"generator": "Gridsome"}},
		{Name: "Bridgetown", Category: "Static Site Generator", Patterns: []string{"bridgetown"}, Meta: map[string]string{"generator": "Bridgetown"}},
		{Name: "Middleman", Category: "Static Site Generator", Patterns: []string{"middleman"}, Meta: map[string]string{"generator": "Middleman"}},
		// === Database (20) ===
		{Name: "MySQL", Category: "Database", Patterns: []string{"mysql", "MySQL"}, Headers: map[string]string{"x-db-server": "mysql"}},
		{Name: "PostgreSQL", Category: "Database", Patterns: []string{"postgresql", "postgres", "pgsql"}},
		{Name: "MongoDB", Category: "Database", Patterns: []string{"mongodb", "mongo"}},
		{Name: "Redis", Category: "Database", Patterns: []string{"redis"}, Headers: map[string]string{"x-redis-": ""}},
		{Name: "Elasticsearch", Category: "Database", Patterns: []string{"elasticsearch", "elastic.co"}, Headers: map[string]string{"x-elastic-product": "Elasticsearch"}},
		{Name: "Neo4j", Category: "Database", Patterns: []string{"neo4j"}},
		{Name: "InfluxDB", Category: "Database", Patterns: []string{"influxdb", "influxdata"}, Headers: map[string]string{"x-influxdb-version": ""}},
		{Name: "CouchDB", Category: "Database", Patterns: []string{"couchdb"}, Headers: map[string]string{"server": "CouchDB"}},
		{Name: "Cassandra", Category: "Database", Patterns: []string{"cassandra"}},
		{Name: "DynamoDB", Category: "Database", Patterns: []string{"dynamodb", "amazonaws.com/dynamodb"}},
		{Name: "Firestore", Category: "Database", Patterns: []string{"firestore.googleapis.com", "firestore"}},
		{Name: "MariaDB", Category: "Database", Patterns: []string{"mariadb", "MariaDB"}},
		{Name: "SQLite", Category: "Database", Patterns: []string{"sqlite"}},
		{Name: "RethinkDB", Category: "Database", Patterns: []string{"rethinkdb"}},
		{Name: "ArangoDB", Category: "Database", Patterns: []string{"arangodb", "arango"}, Headers: map[string]string{"server": "ArangoDB"}},
		{Name: "Supabase DB", Category: "Database", Patterns: []string{"supabase.co", "supabase"}},
		{Name: "PlanetScale DB", Category: "Database", Patterns: []string{"planetscale.com", "planetscale"}},
		{Name: "Fauna", Category: "Database", Patterns: []string{"fauna.com", "faunadb"}},
		{Name: "Cockroach DB", Category: "Database", Patterns: []string{"cockroachlabs.com", "cockroachdb"}},
		{Name: "TiDB", Category: "Database", Patterns: []string{"tidb", "pingcap.com"}},
		// === Message Queue (9) ===
		{Name: "RabbitMQ", Category: "Message Queue", Patterns: []string{"rabbitmq", ":15672"}, Headers: map[string]string{"server": "RabbitMQ"}},
		{Name: "Apache Kafka", Category: "Message Queue", Patterns: []string{"kafka", "confluent"}},
		{Name: "NATS", Category: "Message Queue", Patterns: []string{"nats.io", "nats"}, Headers: map[string]string{"server": "nats"}},
		{Name: "AWS SQS/SNS", Category: "Message Queue", Patterns: []string{"sqs.amazonaws.com", "sns.amazonaws.com"}},
		{Name: "Redis Pub/Sub", Category: "Message Queue", Patterns: []string{"redis", "ioredis", "redis-pub"}},
		{Name: "ZeroMQ", Category: "Message Queue", Patterns: []string{"zeromq", "zmq"}},
		{Name: "Apache Pulsar", Category: "Message Queue", Patterns: []string{"pulsar", "apache-pulsar"}},
		{Name: "ActiveMQ", Category: "Message Queue", Patterns: []string{"activemq", ":8161"}},
		{Name: "Celery", Category: "Message Queue", Patterns: []string{"celery", "celeryproject.org"}},
		// === Caching (11) ===
		{Name: "Varnish Cache", Category: "Caching", Patterns: []string{"varnish"}, Headers: map[string]string{"x-varnish": "", "x-cache": "HIT"}},
		{Name: "Redis Cache", Category: "Caching", Patterns: []string{"redis-cache"}, Headers: map[string]string{"x-redis-cache": ""}},
		{Name: "Memcached", Category: "Caching", Patterns: []string{"memcached"}, Headers: map[string]string{"x-memcached": ""}},
		{Name: "PHP OPcache", Category: "Caching", Patterns: []string{"opcache"}, Headers: map[string]string{"x-powered-by": "PHP"}},
		{Name: "Nginx FastCGI Cache", Category: "Caching", Headers: map[string]string{"x-fastcgi-cache": ""}},
		{Name: "Cloudflare Cache", Category: "Caching", Headers: map[string]string{"cf-cache-status": "HIT"}},
		{Name: "W3 Total Cache", Category: "Caching", HTML: []string{"<!-- W3 Total Cache", "<!-- Performance optimized by W3 Total Cache"}},
		{Name: "LiteSpeed Cache", Category: "Caching", Headers: map[string]string{"x-litespeed-cache": ""}, HTML: []string{"<!-- Page generated by LiteSpeed Cache"}},
		{Name: "Fastly Cache", Category: "Caching", Headers: map[string]string{"x-cache": "", "x-fastly-request-id": ""}},
		{Name: "Akamai Edge Cache", Category: "Caching", Headers: map[string]string{"x-akamai-transformed": "", "x-cache-key": ""}},
		{Name: "KeyDB Cache", Category: "Caching", Patterns: []string{"keydb"}},
		// === Build Tools (10) ===
		{Name: "Webpack", Category: "Build Tool", Patterns: []string{"webpackChunk", "webpack", "__webpack_require__"}, Scripts: []string{"bundle.js", "main.chunk.js"}},
		{Name: "Vite", Category: "Build Tool", Patterns: []string{"/@vite/", "vite", "import.meta.env"}, Scripts: []string{"@vite/client"}},
		{Name: "Parcel", Category: "Build Tool", Patterns: []string{"parcelRequire", "parcel"}},
		{Name: "Rollup", Category: "Build Tool", Patterns: []string{"rollup"}},
		{Name: "esbuild", Category: "Build Tool", Patterns: []string{"esbuild"}},
		{Name: "Turbopack", Category: "Build Tool", Patterns: []string{"turbopack", "TURBOPACK"}},
		{Name: "Gulp", Category: "Build Tool", Patterns: []string{"gulp", "gulpfile"}},
		{Name: "Grunt", Category: "Build Tool", Patterns: []string{"grunt", "Gruntfile"}},
		{Name: "Snowpack", Category: "Build Tool", Patterns: []string{"snowpack"}},
		{Name: "SWC", Category: "Build Tool", Patterns: []string{"swc", "@swc/core"}},
		// === Auth/SSO (12) ===
		{Name: "OAuth 2.0", Category: "Auth/SSO", Patterns: []string{"oauth", "/oauth/authorize", "/oauth/token"}},
		{Name: "SAML", Category: "Auth/SSO", Patterns: []string{"saml", "/saml/", "SAMLRequest"}},
		{Name: "OpenID Connect", Category: "Auth/SSO", Patterns: []string{"openid", "/.well-known/openid-configuration"}},
		{Name: "Auth0", Category: "Auth/SSO", Patterns: []string{"auth0.com", "cdn.auth0.com"}, Scripts: []string{"auth0.min.js"}},
		{Name: "Okta", Category: "Auth/SSO", Patterns: []string{"okta.com", "oktacdn.com"}, Scripts: []string{"okta-signin-widget"}},
		{Name: "CAS (Central Auth)", Category: "Auth/SSO", Patterns: []string{"/cas/login", "cas-server", "cas/serviceValidate"}},
		{Name: "Kerberos", Category: "Auth/SSO", Headers: map[string]string{"www-authenticate": "Negotiate"}},
		{Name: "LDAP", Category: "Auth/SSO", Patterns: []string{"ldap://", "ldaps://"}},
		{Name: "Keycloak", Category: "Auth/SSO", Patterns: []string{"keycloak", "/auth/realms/"}, Cookies: []string{"KEYCLOAK_SESSION", "KEYCLOAK_IDENTITY"}},
		{Name: "AWS Cognito", Category: "Auth/SSO", Patterns: []string{"cognito-idp", "amazoncognito.com"}},
		{Name: "Firebase Auth", Category: "Auth/SSO", Patterns: []string{"firebaseauth", "identitytoolkit.googleapis.com"}},
		{Name: "Ping Identity", Category: "Auth/SSO", Patterns: []string{"pingone.com", "pingidentity"}},
		// === Session Management (9) ===
		{Name: "PHP Session", Category: "Session", Cookies: []string{"PHPSESSID"}},
		{Name: "Java Session (JSESSIONID)", Category: "Session", Cookies: []string{"JSESSIONID"}},
		{Name: "ASP.NET Session", Category: "Session", Cookies: []string{"ASP.NET_SessionId", ".AspNetCore.Session"}},
		{Name: "Express Session (connect.sid)", Category: "Session", Cookies: []string{"connect.sid"}},
		{Name: "Ruby Rack Session", Category: "Session", Cookies: []string{"rack.session", "_session_id"}},
		{Name: "Generic Session", Category: "Session", Cookies: []string{"_session", "session_id", "sid"}},
		{Name: "Django Session", Category: "Session", Cookies: []string{"sessionid"}},
		{Name: "Flask Session", Category: "Session", Cookies: []string{"session"}},
		{Name: "Laravel Session", Category: "Session", Cookies: []string{"laravel_session"}},
		// === E-commerce Extended (11) ===
		{Name: "Shopify Hydrogen", Category: "E-commerce", Patterns: []string{"hydrogen", "shopify/hydrogen"}, Scripts: []string{"@shopify/hydrogen"}},
		{Name: "Medusa Commerce", Category: "E-commerce", Patterns: []string{"medusajs", "medusa-storefront"}},
		{Name: "Saleor", Category: "E-commerce", Patterns: []string{"saleor", "saleor.io"}},
		{Name: "Adyen", Category: "Payment", Patterns: []string{"adyen.com", "checkoutshopper"}, Scripts: []string{"adyen.encrypt.min.js", "adyen-web"}},
		{Name: "Klarna", Category: "Payment", Patterns: []string{"klarna.com", "klarna"}, Scripts: []string{"klarna-payments"}},
		{Name: "Mollie", Category: "Payment", Patterns: []string{"mollie.com", "js.mollie.com"}},
		{Name: "Snipcart", Category: "E-commerce", Patterns: []string{"snipcart.com", "snipcart"}, Scripts: []string{"cdn.snipcart.com"}},
		{Name: "Commerce.js", Category: "E-commerce", Patterns: []string{"commercejs", "chec.io"}, Scripts: []string{"commercejs/sdk"}},
		{Name: "Square Online", Category: "E-commerce", Patterns: []string{"squareonline.com", "square.com/online-store"}},
		{Name: "Paddle", Category: "Payment", Patterns: []string{"paddle.com", "cdn.paddle.com"}, Scripts: []string{"cdn.paddle.com"}},
		{Name: "Recurly", Category: "Payment", Patterns: []string{"recurly.com", "js.recurly.com"}, Scripts: []string{"js.recurly.com"}},
		// === Email Infrastructure (11) ===
		{Name: "Microsoft 365 / Exchange", Category: "Email", Patterns: []string{"outlook.com", "office365.com"}, Headers: map[string]string{"x-ms-exchange-": ""}},
		{Name: "Google Workspace", Category: "Email", Patterns: []string{"google.com/a/", "googlemail.com", "aspmx.l.google.com"}},
		{Name: "SendGrid", Category: "Email", Patterns: []string{"sendgrid.net", "sendgrid.com"}, Headers: map[string]string{"x-sg-eid": ""}},
		{Name: "Amazon SES", Category: "Email", Patterns: []string{"amazonaws.com/ses", "email.amazonses.com"}, Headers: map[string]string{"x-ses-outgoing": ""}},
		{Name: "Postmark", Category: "Email", Patterns: []string{"postmarkapp.com"}, Headers: map[string]string{"x-pm-message-id": ""}},
		{Name: "Mailchimp Transactional", Category: "Email", Patterns: []string{"mandrillapp.com", "mandrill"}},
		{Name: "Mailgun", Category: "Email", Patterns: []string{"mailgun.org", "mailgun.net"}},
		{Name: "SparkPost", Category: "Email", Patterns: []string{"sparkpostmail.com", "sparkpost"}},
		{Name: "Brevo (Sendinblue)", Category: "Email", Patterns: []string{"brevo.com", "sendinblue.com"}},
		{Name: "Resend", Category: "Email", Patterns: []string{"resend.com", "resend"}},
		{Name: "Mailtrap", Category: "Email", Patterns: []string{"mailtrap.io"}},
		// === Marketing (11) ===
		{Name: "HubSpot", Category: "Marketing", Patterns: []string{"hubspot.com", "hs-scripts.com", "hs-analytics.net"}, Scripts: []string{"js.hs-scripts.com", "js.hs-analytics.net"}},
		{Name: "Marketo", Category: "Marketing", Patterns: []string{"marketo.com", "marketo.net", "munchkin"}, Scripts: []string{"munchkin.marketo.net"}},
		{Name: "Pardot (Salesforce)", Category: "Marketing", Patterns: []string{"pardot.com", "pi.pardot.com"}, Scripts: []string{"pi.pardot.com/pd.js"}},
		{Name: "ActiveCampaign", Category: "Marketing", Patterns: []string{"activecampaign.com", "trackcmp.net"}, Scripts: []string{"trackcmp.net"}},
		{Name: "Klaviyo", Category: "Marketing", Patterns: []string{"klaviyo.com", "a.]klaviyo.com"}, Scripts: []string{"static.klaviyo.com"}},
		{Name: "ConvertKit", Category: "Marketing", Patterns: []string{"convertkit.com", "convertkit"}, Scripts: []string{"convertkit.com/js/"}},
		{Name: "Drip", Category: "Marketing", Patterns: []string{"getdrip.com", "drip"}, Scripts: []string{"dc.getdrip.com"}},
		{Name: "Mailchimp Marketing", Category: "Marketing", Patterns: []string{"mailchimp.com", "list-manage.com"}, Scripts: []string{"chimpstatic.com"}},
		{Name: "Brevo Marketing", Category: "Marketing", Patterns: []string{"brevo.com", "sendinblue.com"}, Scripts: []string{"sibautomation.com"}},
		{Name: "Unbounce", Category: "Marketing", Patterns: []string{"unbounce.com"}, Scripts: []string{"ubembed.com"}},
		{Name: "Instapage", Category: "Marketing", Patterns: []string{"instapage.com"}},
		// === Bot Protection (8) ===
		{Name: "PerimeterX", Category: "Bot Protection", Patterns: []string{"perimeterx.net", "px.js"}, Cookies: []string{"_pxhd", "_pxvid"}, Scripts: []string{"client.perimeterx.net"}},
		{Name: "DataDome", Category: "Bot Protection", Patterns: []string{"datadome.co"}, Cookies: []string{"datadome"}, Scripts: []string{"js.datadome.co"}},
		{Name: "Shape Security", Category: "Bot Protection", Patterns: []string{"shape.com"}, Headers: map[string]string{"x-shape-": ""}},
		{Name: "Kasada", Category: "Bot Protection", Patterns: []string{"kasada.io"}, Headers: map[string]string{"x-kpsdk-ct": ""}, Scripts: []string{"ips.js"}},
		{Name: "Distil Networks", Category: "Bot Protection", Headers: map[string]string{"x-distil-cs": ""}, Cookies: []string{"D_SID", "D_AWSALB"}},
		{Name: "HUMAN Security", Category: "Bot Protection", Patterns: []string{"humansecurity.com", "perimeterx"}, Cookies: []string{"_px3"}},
		{Name: "Akamai Bot Manager", Category: "Bot Protection", Headers: map[string]string{"akamai-grn": ""}, Cookies: []string{"ak_bmsc", "_abck"}},
		{Name: "Cloudflare Bot Management", Category: "Bot Protection", Headers: map[string]string{"cf-bot-score": ""}, Cookies: []string{"cf_clearance"}},
		// === DDoS Protection (6) ===
		{Name: "Akamai Prolexic", Category: "DDoS Protection", Patterns: []string{"prolexic", "akamai.com"}, Headers: map[string]string{"x-akamai-edge-log": ""}},
		{Name: "AWS Shield", Category: "DDoS Protection", Patterns: []string{"aws.amazon.com/shield"}, Headers: map[string]string{"x-amzn-ddos-": ""}},
		{Name: "Radware DefensePro", Category: "DDoS Protection", Patterns: []string{"radware.com"}, Headers: map[string]string{"x-rdwr-": ""}},
		{Name: "Arbor Networks / NETSCOUT", Category: "DDoS Protection", Patterns: []string{"arbornetworks.com", "netscout.com"}},
		{Name: "Cloudflare DDoS Protection", Category: "DDoS Protection", Patterns: []string{"cloudflare.com"}, Headers: map[string]string{"cf-ray": ""}},
		{Name: "Imperva DDoS Protection", Category: "DDoS Protection", Patterns: []string{"incapsula.com"}, Headers: map[string]string{"x-iinfo": ""}},
		// === Cookie Consent (8) ===
		{Name: "OneTrust", Category: "Cookie Consent", Patterns: []string{"onetrust.com", "optanon"}, Cookies: []string{"OptanonConsent", "OptanonAlertBoxClosed"}, Scripts: []string{"cdn.cookielaw.org", "otBannerSdk.js"}},
		{Name: "Cookiebot", Category: "Cookie Consent", Patterns: []string{"cookiebot.com"}, Cookies: []string{"CookieConsent"}, Scripts: []string{"consent.cookiebot.com"}},
		{Name: "CookieYes", Category: "Cookie Consent", Patterns: []string{"cookieyes.com"}, Cookies: []string{"cookieyes-consent"}, Scripts: []string{"cdn-cookieyes.com"}},
		{Name: "Osano", Category: "Cookie Consent", Patterns: []string{"osano.com"}, Scripts: []string{"cmp.osano.com"}},
		{Name: "TrustArc", Category: "Cookie Consent", Patterns: []string{"trustarc.com", "truste.com"}, Cookies: []string{"notice_preferences"}, Scripts: []string{"consent.trustarc.com"}},
		{Name: "Complianz", Category: "Cookie Consent", Patterns: []string{"complianz", "cmplz"}, Cookies: []string{"cmplz_consent_status"}, Scripts: []string{"complianz/assets/js/"}},
		{Name: "TermsFeed", Category: "Cookie Consent", Patterns: []string{"termsfeed.com"}, Scripts: []string{"termsfeed.com/cookie-consent"}},
		{Name: "Iubenda", Category: "Cookie Consent", Patterns: []string{"iubenda.com"}, Cookies: []string{"_iub_cs-"}, Scripts: []string{"cdn.iubenda.com"}},
		// === VPN/Remote Access (8) ===
		{Name: "Pulse Secure (Ivanti)", Category: "VPN/Remote Access", Patterns: []string{"/dana-na/", "pulse", "pulsesecure"}, Cookies: []string{"DSID", "DSSignInURL"}},
		{Name: "Citrix Gateway (NetScaler)", Category: "VPN/Remote Access", Patterns: []string{"/vpn/", "citrix", "netscaler"}, Cookies: []string{"NSC_", "CITRIX_"}},
		{Name: "GlobalProtect (Palo Alto)", Category: "VPN/Remote Access", Patterns: []string{"/global-protect/", "globalprotect"}},
		{Name: "Fortinet VPN (FortiGate)", Category: "VPN/Remote Access", Patterns: []string{"/remote/login", "fortinet"}, Cookies: []string{"SVPNCOOKIE"}},
		{Name: "OpenVPN Access Server", Category: "VPN/Remote Access", Patterns: []string{"openvpn", "/admin/", "OpenVPN"}},
		{Name: "WireGuard", Category: "VPN/Remote Access", Patterns: []string{"wireguard", "wg0"}},
		{Name: "Cisco AnyConnect", Category: "VPN/Remote Access", Patterns: []string{"anyconnect", "+CSCOE+/", "+CSCOT+/"}},
		{Name: "SonicWall VPN", Category: "VPN/Remote Access", Patterns: []string{"sonicwall", "cgi-bin/welcome"}, Cookies: []string{"SonicWALL"}},
		// === Firewall (11) ===
		{Name: "Palo Alto PAN-OS", Category: "Firewall", Patterns: []string{"/php/login.php", "paloaltonetworks", "PAN-OS"}},
		{Name: "FortiGate (FortiOS)", Category: "Firewall", Patterns: []string{"fortinet", "fortigate"}, Cookies: []string{"APSCOOKIE_"}},
		{Name: "Check Point", Category: "Firewall", Patterns: []string{"checkpoint", "Check Point"}, Headers: map[string]string{"server": "Check Point"}},
		{Name: "pfSense", Category: "Firewall", Patterns: []string{"pfsense", "pfSense"}, Cookies: []string{"cookie_pfsense_"}},
		{Name: "Sophos XG", Category: "Firewall", Patterns: []string{"sophos", "Sophos"}},
		{Name: "MikroTik RouterOS", Category: "Firewall", Patterns: []string{"mikrotik", "routeros", "RouterOS"}},
		{Name: "Barracuda Firewall", Category: "Firewall", Patterns: []string{"barracuda", "Barracuda"}},
		{Name: "Juniper SRX", Category: "Firewall", Patterns: []string{"juniper", "junos", "SRX"}},
		{Name: "Cisco ASA", Category: "Firewall", Patterns: []string{"cisco", "CSCOE", "Cisco ASA"}},
		{Name: "Zyxel Firewall", Category: "Firewall", Patterns: []string{"zyxel", "ZyWALL"}},
		{Name: "WatchGuard", Category: "Firewall", Patterns: []string{"watchguard", "WatchGuard"}},
		// === Collaboration (12) ===
		{Name: "SharePoint", Category: "Collaboration", Patterns: []string{"sharepoint.com", "/_layouts/", "microsoftonline.com"}, Headers: map[string]string{"sprequestguid": "", "microsoftsharepointteamservices": ""}},
		{Name: "Confluence", Category: "Collaboration", Patterns: []string{"/confluence/", "atlassian.net", "confluence"}, Cookies: []string{"JSESSIONID", "confluence.browse.space.cookie"}},
		{Name: "Jira", Category: "Collaboration", Patterns: []string{"/jira/", "atlassian.net/browse/", "jira"}, Cookies: []string{"atlassian.xsrf.token"}},
		{Name: "Nextcloud", Category: "Collaboration", Patterns: []string{"/nextcloud/", "nextcloud"}, Headers: map[string]string{"x-nextcloud-": ""}, Cookies: []string{"nc_session_id"}},
		{Name: "Moodle", Category: "Collaboration", Patterns: []string{"moodle", "/moodle/"}, Cookies: []string{"MoodleSession"}},
		{Name: "GitLab", Category: "Collaboration", Patterns: []string{"gitlab", "/gitlab/"}, Cookies: []string{"_gitlab_session"}, Meta: map[string]string{"og:site_name": "GitLab"}},
		{Name: "Redmine", Category: "Collaboration", Patterns: []string{"redmine", "/redmine/"}, Cookies: []string{"_redmine_session"}, Meta: map[string]string{"description": "Redmine"}},
		{Name: "Discourse", Category: "Collaboration", Patterns: []string{"discourse", "discourse-"}, Cookies: []string{"_forum_session"}, Meta: map[string]string{"generator": "Discourse"}},
		{Name: "Rocket.Chat", Category: "Collaboration", Patterns: []string{"rocket.chat"}, Cookies: []string{"rc_uid", "rc_token"}},
		{Name: "Mattermost", Category: "Collaboration", Patterns: []string{"mattermost"}, Cookies: []string{"MMAUTHTOKEN"}},
		{Name: "Notion", Category: "Collaboration", Patterns: []string{"notion.so", "notion.com"}},
		{Name: "Basecamp", Category: "Collaboration", Patterns: []string{"basecamp.com", "basecampapi.com"}},
		// === Ticketing (8) ===
		{Name: "ServiceNow", Category: "Ticketing", Patterns: []string{"service-now.com", "servicenow"}, Cookies: []string{"glide_user_route", "sysparm_"}},
		{Name: "Zendesk", Category: "Ticketing", Patterns: []string{"zendesk.com", "zdassets.com"}, Cookies: []string{"_zendesk_session"}},
		{Name: "Freshdesk", Category: "Ticketing", Patterns: []string{"freshdesk.com", "freshworks"}, Cookies: []string{"_helpkit_session"}},
		{Name: "OTRS", Category: "Ticketing", Patterns: []string{"otrs", "/otrs/"}, Cookies: []string{"OTRSAgentInterface"}},
		{Name: "osTicket", Category: "Ticketing", Patterns: []string{"osticket", "/scp/"}, Cookies: []string{"OSTSESSID"}},
		{Name: "Request Tracker (RT)", Category: "Ticketing", Patterns: []string{"/rt/", "request-tracker", "Best Practical"}, Cookies: []string{"RT_SID_"}},
		{Name: "Jira Service Management", Category: "Ticketing", Patterns: []string{"/servicedesk/", "jira-servicedesk"}},
		{Name: "Help Scout", Category: "Ticketing", Patterns: []string{"helpscout.net", "beacon-v2.helpscout.net"}, Scripts: []string{"beacon-v2.helpscout.net"}},
		// === CRM/ERP (9) ===
		{Name: "Salesforce", Category: "CRM/ERP", Patterns: []string{"force.com", "salesforce.com", "lightning"}, Cookies: []string{"sfdc-stream"}},
		{Name: "SAP", Category: "CRM/ERP", Patterns: []string{"sap.com", "/sap/", "sapui5"}, Cookies: []string{"sap-usercontext"}},
		{Name: "Oracle", Category: "CRM/ERP", Patterns: []string{"oracle.com", "/OA_HTML/"}, Cookies: []string{"ORA_"}},
		{Name: "Dynamics 365", Category: "CRM/ERP", Patterns: []string{"dynamics.com", "crm.dynamics.com"}, Cookies: []string{"ReqClientId"}},
		{Name: "Odoo", Category: "CRM/ERP", Patterns: []string{"odoo", "/web/login"}, Cookies: []string{"session_id"}, Meta: map[string]string{"generator": "Odoo"}},
		{Name: "Zoho CRM", Category: "CRM/ERP", Patterns: []string{"zoho.com", "crm.zoho.com"}, Cookies: []string{"JSESSIONID", "iamcsr"}},
		{Name: "SugarCRM", Category: "CRM/ERP", Patterns: []string{"sugarcrm", "/sugarcrm/"}, Cookies: []string{"sugar_user_theme"}},
		{Name: "HubSpot CRM", Category: "CRM/ERP", Patterns: []string{"hubspot.com", "app.hubspot.com"}},
		{Name: "NetSuite", Category: "CRM/ERP", Patterns: []string{"netsuite.com", "system.netsuite.com"}, Cookies: []string{"NS_VER", "NLSESSIONID"}},
		// === Management UI (20) ===
		{Name: "phpMyAdmin", Category: "Management UI", Patterns: []string{"phpmyadmin", "/phpmyadmin/"}, Cookies: []string{"phpMyAdmin", "pma_lang"}},
		{Name: "Grafana", Category: "Management UI", Patterns: []string{"grafana", "/grafana/"}, Headers: map[string]string{"x-grafana-": ""}, Cookies: []string{"grafana_session"}},
		{Name: "Jenkins", Category: "Management UI", Patterns: []string{"jenkins", "/jenkins/"}, Headers: map[string]string{"x-jenkins": ""}, Cookies: []string{"JSESSIONID."}},
		{Name: "Kibana", Category: "Management UI", Patterns: []string{"kibana", "/app/kibana"}, Headers: map[string]string{"kbn-name": ""}},
		{Name: "Portainer", Category: "Management UI", Patterns: []string{"portainer", "/api/endpoints"}, Cookies: []string{"portainer.SID"}},
		{Name: "Kubernetes Dashboard", Category: "Management UI", Patterns: []string{"/api/v1/namespaces/kubernetes-dashboard", "kubernetes-dashboard"}},
		{Name: "pgAdmin", Category: "Management UI", Patterns: []string{"pgadmin", "/pgadmin4/"}, Cookies: []string{"pga4_session"}},
		{Name: "Adminer", Category: "Management UI", Patterns: []string{"adminer", "adminer.php"}},
		{Name: "MongoDB Compass Web", Category: "Management UI", Patterns: []string{"mongodb-compass", "mongo-express"}},
		{Name: "Prometheus UI", Category: "Management UI", Patterns: []string{"prometheus", "/graph", "/api/v1/query"}},
		{Name: "Consul UI", Category: "Management UI", Patterns: []string{"consul", "/ui/", "/v1/catalog/"}},
		{Name: "Vault UI", Category: "Management UI", Patterns: []string{"vault", "/ui/vault/"}, Headers: map[string]string{"x-vault-": ""}},
		{Name: "ArgoCD", Category: "Management UI", Patterns: []string{"argocd", "/applications"}, Cookies: []string{"argocd.token"}},
		{Name: "GitLab CI", Category: "Management UI", Patterns: []string{"gitlab-ci", "/-/pipelines/", "gitlab-runner"}},
		{Name: "Nagios", Category: "Management UI", Patterns: []string{"nagios", "/nagios/", "/cgi-bin/nagios"}, Cookies: []string{"nagios"}},
		{Name: "Zabbix", Category: "Management UI", Patterns: []string{"zabbix", "/zabbix/"}, Cookies: []string{"zbx_sessionid"}},
		{Name: "Cockpit", Category: "Management UI", Patterns: []string{"cockpit-ws", "/cockpit/"}},
		{Name: "Traefik Dashboard", Category: "Management UI", Patterns: []string{"/dashboard/", "traefik"}},
		{Name: "SonarQube", Category: "Management UI", Patterns: []string{"sonarqube", "/sonar/"}, Cookies: []string{"XSRF-TOKEN"}},
		{Name: "Rundeck", Category: "Management UI", Patterns: []string{"rundeck", "/rundeck/"}, Cookies: []string{"RUNDECK_SESSION_TOKEN"}},
		// === Monitoring (11) ===
		{Name: "Datadog RUM", Category: "Monitoring", Patterns: []string{"datadoghq.com", "dd_rum"}, Scripts: []string{"datadog-rum.js", "dd-rum-sdk"}},
		{Name: "New Relic Browser", Category: "Monitoring", Patterns: []string{"newrelic.com", "nr-data.net", "NREUM"}, Scripts: []string{"nr-spa", "newrelic.js"}},
		{Name: "Dynatrace", Category: "Monitoring", Patterns: []string{"dynatrace.com", "dynaTrace"}, Headers: map[string]string{"x-dynatrace": ""}, Scripts: []string{"ruxitagentjs"}},
		{Name: "Elastic APM", Category: "Monitoring", Patterns: []string{"elastic-apm", "apm-server"}, Scripts: []string{"elastic-apm-rum.umd.min.js"}},
		{Name: "Jaeger", Category: "Monitoring", Patterns: []string{"jaeger", "/api/traces"}, Headers: map[string]string{"uber-trace-id": ""}},
		{Name: "Zipkin", Category: "Monitoring", Patterns: []string{"zipkin", "/api/v2/spans"}, Headers: map[string]string{"x-b3-traceid": ""}},
		{Name: "AppDynamics", Category: "Monitoring", Patterns: []string{"appdynamics.com"}, Scripts: []string{"adrum.min.js", "adrum-"}},
		{Name: "Instana", Category: "Monitoring", Patterns: []string{"instana.io"}, Scripts: []string{"eum.instana.io"}},
		{Name: "Grafana RUM (Faro)", Category: "Monitoring", Patterns: []string{"grafana-faro", "@grafana/faro"}, Scripts: []string{"unpkg.com/@grafana/faro-web-sdk"}},
		{Name: "Prometheus", Category: "Monitoring", Patterns: []string{"prometheus", "/api/v1/query"}},
		{Name: "Honeycomb", Category: "Monitoring", Patterns: []string{"honeycomb.io"}, Scripts: []string{"cdn.honeycomb.io"}},
		// === Logging (8) ===
		{Name: "ELK Stack (Kibana)", Category: "Logging", Patterns: []string{"kibana", "/app/discover"}, Headers: map[string]string{"kbn-name": "", "kbn-version": ""}},
		{Name: "Splunk", Category: "Logging", Patterns: []string{"splunk", "/en-US/app/"}, Headers: map[string]string{"x-splunk-": ""}, Cookies: []string{"splunkd_"}},
		{Name: "Graylog", Category: "Logging", Patterns: []string{"graylog", "/graylog/"}},
		{Name: "Grafana Loki", Category: "Logging", Patterns: []string{"loki", "/loki/api/v1/"}},
		{Name: "Fluentd", Category: "Logging", Patterns: []string{"fluentd", "td-agent"}},
		{Name: "Papertrail", Category: "Logging", Patterns: []string{"papertrailapp.com", "papertrail"}},
		{Name: "Seq", Category: "Logging", Patterns: []string{"seq", "datalust.co"}},
		{Name: "Vector (Datadog)", Category: "Logging", Patterns: []string{"vector.dev", "vector"}},
		// === Chat Widget (10) ===
		{Name: "Intercom", Category: "Chat Widget", Patterns: []string{"intercom.io", "intercomcdn.com"}, Scripts: []string{"widget.intercom.io"}},
		{Name: "Drift", Category: "Chat Widget", Patterns: []string{"drift.com", "js.driftt.com"}, Scripts: []string{"js.driftt.com"}},
		{Name: "Tawk.to", Category: "Chat Widget", Patterns: []string{"tawk.to"}, Scripts: []string{"embed.tawk.to"}},
		{Name: "LiveChat", Category: "Chat Widget", Patterns: []string{"livechatinc.com"}, Scripts: []string{"cdn.livechatinc.com"}},
		{Name: "Crisp", Category: "Chat Widget", Patterns: []string{"crisp.chat"}, Scripts: []string{"client.crisp.chat"}},
		{Name: "Tidio", Category: "Chat Widget", Patterns: []string{"tidio.co"}, Scripts: []string{"code.tidio.co"}},
		{Name: "Olark", Category: "Chat Widget", Patterns: []string{"olark.com"}, Scripts: []string{"static.olark.com"}},
		{Name: "Freshchat", Category: "Chat Widget", Patterns: []string{"freshchat", "wchat.freshchat.com"}, Scripts: []string{"wchat.freshchat.com"}},
		{Name: "Zendesk Chat", Category: "Chat Widget", Patterns: []string{"zopim.com"}, Scripts: []string{"v2.zopim.com"}},
		{Name: "HubSpot Chat", Category: "Chat Widget", Patterns: []string{"hubspot.com/conversations"}, Scripts: []string{"js.usemessages.com"}},
		// === Comment System (6) ===
		{Name: "Disqus", Category: "Comment System", Patterns: []string{"disqus.com", "disquscdn.com"}, Scripts: []string{"disqus.com/embed.js"}, HTML: []string{"<div id=\"disqus_thread\""}},
		{Name: "Commento", Category: "Comment System", Patterns: []string{"commento.io", "commento"}, Scripts: []string{"cdn.commento.io"}},
		{Name: "Isso", Category: "Comment System", Patterns: []string{"isso", "/isso/js/embed.min.js"}, Scripts: []string{"isso/js/embed.min.js"}},
		{Name: "Hyvor Talk", Category: "Comment System", Patterns: []string{"hyvor.com", "talk.hyvor.com"}, Scripts: []string{"talk.hyvor.com/web-api/embed"}},
		{Name: "Giscus", Category: "Comment System", Patterns: []string{"giscus.app"}, Scripts: []string{"giscus.app/client.js"}},
		{Name: "Utterances", Category: "Comment System", Patterns: []string{"utteranc.es"}, Scripts: []string{"utteranc.es/client.js"}},
		// === Push Notification (6) ===
		{Name: "OneSignal", Category: "Push Notification", Patterns: []string{"onesignal.com"}, Scripts: []string{"cdn.onesignal.com/sdks/OneSignalSDK.js"}},
		{Name: "Firebase Cloud Messaging", Category: "Push Notification", Patterns: []string{"firebase-messaging", "fcm.googleapis.com"}, Scripts: []string{"firebase-messaging-sw.js"}},
		{Name: "Pusher", Category: "Push Notification", Patterns: []string{"pusher.com", "js.pusher.com"}, Scripts: []string{"js.pusher.com"}},
		{Name: "PushEngage", Category: "Push Notification", Patterns: []string{"pushengage.com"}, Scripts: []string{"clientcdn.pushengage.com"}},
		{Name: "Web Push Protocol", Category: "Push Notification", Patterns: []string{"PushManager", "pushSubscription", "serviceWorker"}},
		{Name: "Aimtell", Category: "Push Notification", Patterns: []string{"aimtell.com"}, Scripts: []string{"cdn.aimtell.com"}},
		// === Media Optimization (9) ===
		{Name: "Cloudinary", Category: "Media Optimization", Patterns: []string{"cloudinary.com", "res.cloudinary.com"}},
		{Name: "imgix", Category: "Media Optimization", Patterns: []string{"imgix.net", "imgix.com"}},
		{Name: "Imagify", Category: "Media Optimization", Patterns: []string{"imagify.io", "/wp-content/plugins/imagify/"}},
		{Name: "ShortPixel", Category: "Media Optimization", Patterns: []string{"shortpixel.com", "shortpixel.ai"}},
		{Name: "Lazy Loading", Category: "Media Optimization", Patterns: []string{"loading=\"lazy\"", "lazyload", "lazysizes"}, Scripts: []string{"lazysizes.min.js"}},
		{Name: "Thumbor", Category: "Media Optimization", Patterns: []string{"thumbor", "/unsafe/"}},
		{Name: "TinyPNG", Category: "Media Optimization", Patterns: []string{"tinypng.com", "tinify"}},
		{Name: "WebP Detection", Category: "Media Optimization", Patterns: []string{".webp", "image/webp"}},
		{Name: "AVIF Detection", Category: "Media Optimization", Patterns: []string{".avif", "image/avif"}},
		// === Font Service (6) ===
		{Name: "Google Fonts", Category: "Font Service", Patterns: []string{"fonts.googleapis.com", "fonts.gstatic.com"}},
		{Name: "Adobe Fonts (Typekit)", Category: "Font Service", Patterns: []string{"use.typekit.net", "typekit.com", "p.typekit.net"}},
		{Name: "Font Awesome", Category: "Font Service", Patterns: []string{"fontawesome.com", "font-awesome", "fa-"}, Scripts: []string{"kit.fontawesome.com", "fontawesome"}},
		{Name: "Fontastic", Category: "Font Service", Patterns: []string{"fontastic.me", "file.myfontastic.com"}},
		{Name: "Bunny Fonts", Category: "Font Service", Patterns: []string{"fonts.bunny.net"}},
		{Name: "Fontsource", Category: "Font Service", Patterns: []string{"fontsource", "@fontsource"}},
		// === Accessibility (6) ===
		{Name: "accessiBe", Category: "Accessibility", Patterns: []string{"accessibe.com", "acsbapp.com"}, Scripts: []string{"acsbapp.com/apps/app/dist/js/app.js"}},
		{Name: "UserWay", Category: "Accessibility", Patterns: []string{"userway.org"}, Scripts: []string{"cdn.userway.org/widget.js"}},
		{Name: "AudioEye", Category: "Accessibility", Patterns: []string{"audioeye.com"}, Scripts: []string{"ws.audioeye.com"}},
		{Name: "EqualWeb", Category: "Accessibility", Patterns: []string{"equalweb.com"}, Scripts: []string{"cdn.equalweb.com"}},
		{Name: "Texthelp", Category: "Accessibility", Patterns: []string{"texthelp.com", "browsealoud"}, Scripts: []string{"browsealoud.com"}},
		{Name: "Siteimprove", Category: "Accessibility", Patterns: []string{"siteimprove.com"}, Scripts: []string{"siteimproveanalytics.com"}},
		// === Social Integration (7) ===
		{Name: "Facebook SDK", Category: "Social Integration", Patterns: []string{"connect.facebook.net", "facebook.com/plugins"}, Scripts: []string{"connect.facebook.net/en_US/sdk.js"}},
		{Name: "Twitter Widgets", Category: "Social Integration", Patterns: []string{"platform.twitter.com", "twitter-widget"}, Scripts: []string{"platform.twitter.com/widgets.js"}},
		{Name: "Google Sign-In", Category: "Social Integration", Patterns: []string{"accounts.google.com/gsi", "g_id_onload"}, Scripts: []string{"accounts.google.com/gsi/client"}},
		{Name: "AddThis / ShareThis", Category: "Social Integration", Patterns: []string{"addthis.com", "sharethis.com"}, Scripts: []string{"s7.addthis.com", "platform-api.sharethis.com"}},
		{Name: "LinkedIn SDK", Category: "Social Integration", Patterns: []string{"platform.linkedin.com"}, Scripts: []string{"platform.linkedin.com/in.js"}},
		{Name: "Instagram Embed", Category: "Social Integration", Patterns: []string{"instagram.com/embed", "instagram.com/p/"}},
		{Name: "Pinterest Widget", Category: "Social Integration", Patterns: []string{"assets.pinterest.com"}, Scripts: []string{"assets.pinterest.com/js/pinit.js"}},
		// === Maps (6) ===
		{Name: "Google Maps", Category: "Maps", Patterns: []string{"maps.googleapis.com", "maps.google.com"}, Scripts: []string{"maps.googleapis.com/maps/api/js"}},
		{Name: "Mapbox", Category: "Maps", Patterns: []string{"api.mapbox.com", "mapbox.com"}, Scripts: []string{"api.mapbox.com/mapbox-gl-js/"}},
		{Name: "Leaflet / OpenStreetMap", Category: "Maps", Patterns: []string{"leafletjs.com", "tile.openstreetmap.org", "L.map"}, Scripts: []string{"leaflet.js", "leaflet.min.js"}},
		{Name: "HERE Maps", Category: "Maps", Patterns: []string{"js.api.here.com", "here.com"}, Scripts: []string{"js.api.here.com"}},
		{Name: "Bing Maps", Category: "Maps", Patterns: []string{"bing.com/maps", "virtualearth.net"}, Scripts: []string{"bing.com/api/maps"}},
		{Name: "TomTom Maps", Category: "Maps", Patterns: []string{"api.tomtom.com"}, Scripts: []string{"api.tomtom.com/maps-sdk-for-web"}},
		// === Video/Media (9) ===
		{Name: "YouTube Embed", Category: "Video/Media", Patterns: []string{"youtube.com/embed", "youtube-nocookie.com"}, HTML: []string{"<iframe src=\"https://www.youtube.com/embed/"}},
		{Name: "Vimeo", Category: "Video/Media", Patterns: []string{"player.vimeo.com", "vimeo.com"}, Scripts: []string{"player.vimeo.com/api/"}},
		{Name: "Wistia", Category: "Video/Media", Patterns: []string{"wistia.com", "wistia.net"}, Scripts: []string{"fast.wistia.com", "wistia.net/assets/external/E-v1.js"}},
		{Name: "JW Player", Category: "Video/Media", Patterns: []string{"jwplayer.com", "jwplatform.com"}, Scripts: []string{"cdn.jwplayer.com/libraries/"}},
		{Name: "Brightcove", Category: "Video/Media", Patterns: []string{"brightcove.com", "brightcovecdn.com"}, Scripts: []string{"players.brightcove.net"}},
		{Name: "Video.js", Category: "Video/Media", Patterns: []string{"video.js", "videojs"}, Scripts: []string{"vjs.zencdn.net", "video.min.js"}},
		{Name: "Vidyard", Category: "Video/Media", Patterns: []string{"vidyard.com"}, Scripts: []string{"play.vidyard.com"}},
		{Name: "Plyr", Category: "Video/Media", Patterns: []string{"plyr", "plyr.js"}, Scripts: []string{"cdn.plyr.io"}},
		{Name: "Cloudflare Stream", Category: "Video/Media", Patterns: []string{"cloudflarestream.com", "videodelivery.net"}},
		// === Form/Survey (6) ===
		{Name: "Google Forms", Category: "Form/Survey", Patterns: []string{"docs.google.com/forms", "forms.gle"}},
		{Name: "Typeform", Category: "Form/Survey", Patterns: []string{"typeform.com"}, Scripts: []string{"embed.typeform.com"}},
		{Name: "JotForm", Category: "Form/Survey", Patterns: []string{"jotform.com", "jotformcdn.com"}, Scripts: []string{"cdn.jotfor.ms"}},
		{Name: "SurveyMonkey", Category: "Form/Survey", Patterns: []string{"surveymonkey.com"}},
		{Name: "Tally", Category: "Form/Survey", Patterns: []string{"tally.so"}, Scripts: []string{"tally.so/widgets/embed.js"}},
		{Name: "Formstack", Category: "Form/Survey", Patterns: []string{"formstack.com"}, Scripts: []string{"formstack.com/forms/js.php"}},
		// === A/B Testing (6) ===
		{Name: "Optimizely", Category: "A/B Testing", Patterns: []string{"optimizely.com"}, Cookies: []string{"optimizelyEndUserId"}, Scripts: []string{"cdn.optimizely.com"}},
		{Name: "VWO (Visual Website Optimizer)", Category: "A/B Testing", Patterns: []string{"visualwebsiteoptimizer.com", "vwo_"}, Cookies: []string{"_vwo_uuid"}, Scripts: []string{"dev.visualwebsiteoptimizer.com"}},
		{Name: "LaunchDarkly", Category: "A/B Testing", Patterns: []string{"launchdarkly.com"}, Scripts: []string{"app.launchdarkly.com"}},
		{Name: "Split.io", Category: "A/B Testing", Patterns: []string{"split.io"}, Scripts: []string{"cdn.split.io"}},
		{Name: "Google Optimize", Category: "A/B Testing", Patterns: []string{"optimize.google.com"}, Scripts: []string{"optimize.google.com/optimize.js"}},
		{Name: "AB Tasty", Category: "A/B Testing", Patterns: []string{"abtasty.com"}, Scripts: []string{"try.abtasty.com"}},
		// === Error Tracking (6) ===
		{Name: "Sentry", Category: "Error Tracking", Patterns: []string{"sentry.io", "@sentry/browser"}, Scripts: []string{"browser.sentry-cdn.com", "sentry.min.js"}},
		{Name: "Bugsnag", Category: "Error Tracking", Patterns: []string{"bugsnag.com"}, Scripts: []string{"d2wy8f7a9ursnm.cloudfront.net/bugsnag"}},
		{Name: "Rollbar", Category: "Error Tracking", Patterns: []string{"rollbar.com"}, Scripts: []string{"cdn.rollbar.com/rollbarjs/"}},
		{Name: "LogRocket", Category: "Error Tracking", Patterns: []string{"logrocket.com", "logrocket"}, Scripts: []string{"cdn.lr-ingest.io"}},
		{Name: "Airbrake", Category: "Error Tracking", Patterns: []string{"airbrake.io"}, Scripts: []string{"notifier-js.airbrake.io"}},
		{Name: "Raygun", Category: "Error Tracking", Patterns: []string{"raygun.com"}, Scripts: []string{"cdn.raygun.io"}},
		// === Internationalization (i18n) (6) ===
		{Name: "Weglot", Category: "i18n", Patterns: []string{"weglot.com", "cdn.weglot.com"}, Scripts: []string{"cdn.weglot.com/weglot.min.js"}},
		{Name: "Crowdin", Category: "i18n", Patterns: []string{"crowdin.com", "jipt.crowdin.com"}, Scripts: []string{"cdn.crowdin.com"}},
		{Name: "Transifex", Category: "i18n", Patterns: []string{"transifex.com", "cdn.transifex.com"}, Scripts: []string{"cdn.transifex.com"}},
		{Name: "hreflang Detection", Category: "i18n", Patterns: []string{"rel=\"alternate\" hreflang="}, HTML: []string{"hreflang="}},
		{Name: "Lokalise", Category: "i18n", Patterns: []string{"lokalise.com"}},
		{Name: "Phrase (Memsource)", Category: "i18n", Patterns: []string{"phrase.com", "memsource"}},
		// === DNS Provider (6) ===
		{Name: "AWS Route 53", Category: "DNS Provider", Patterns: []string{"awsdns-", "route53.amazonaws.com"}},
		{Name: "Azure DNS", Category: "DNS Provider", Patterns: []string{"azure-dns.com", "azuredns-"}},
		{Name: "Google Cloud DNS", Category: "DNS Provider", Patterns: []string{"googledomains.com", "ns-cloud-"}},
		{Name: "Cloudflare DNS", Category: "DNS Provider", Patterns: []string{"ns.cloudflare.com", "cloudflare.com"}},
		{Name: "DNSimple", Category: "DNS Provider", Patterns: []string{"dnsimple.com"}},
		{Name: "NS1", Category: "DNS Provider", Patterns: []string{"nsone.net", "ns1.com"}},
		// === Java App Server (6) ===
		{Name: "Apache Tomcat", Category: "Java App Server", Patterns: []string{"tomcat", "/manager/html"}, Headers: map[string]string{"server": "Apache-Coyote"}, Cookies: []string{"JSESSIONID"}},
		{Name: "JBoss/WildFly", Category: "Java App Server", Patterns: []string{"jboss", "wildfly"}, Headers: map[string]string{"x-powered-by": "JBoss"}},
		{Name: "GlassFish", Category: "Java App Server", Patterns: []string{"glassfish"}, Headers: map[string]string{"server": "GlassFish"}},
		{Name: "Jetty", Category: "Java App Server", Patterns: []string{"org.eclipse.jetty"}, Headers: map[string]string{"server": "Jetty"}},
		{Name: "WebLogic", Category: "Java App Server", Patterns: []string{"weblogic", "/console/"}, Headers: map[string]string{"server": "WebLogic"}},
		{Name: "WebSphere", Category: "Java App Server", Patterns: []string{"websphere"}, Headers: map[string]string{"server": "WebSphere"}},
		// === JVM Frameworks (5) ===
		{Name: "Spring Boot", Category: "JVM Framework", Patterns: []string{"spring", "whitelabel error"}, Headers: map[string]string{"x-application-context": ""}},
		{Name: "Grails", Category: "JVM Framework", Patterns: []string{"grails", "org.grails"}},
		{Name: "Play Framework", Category: "JVM Framework", Patterns: []string{"play framework"}, Cookies: []string{"PLAY_SESSION"}},
		{Name: "Micronaut", Category: "JVM Framework", Patterns: []string{"micronaut"}},
		{Name: "Quarkus", Category: "JVM Framework", Patterns: []string{"quarkus"}},
		// === Other Language Frameworks (4) ===
		{Name: "Elixir Phoenix", Category: "Framework", Patterns: []string{"phx-", "phoenix"}, Cookies: []string{"_csrf_token"}},
		{Name: "Rust Actix", Category: "Framework", Patterns: []string{"actix"}},
		{Name: "Rust Rocket", Category: "Framework", Patterns: []string{"Rocket"}},
		{Name: "Perl Catalyst", Category: "Framework", Patterns: []string{"catalyst", "Catalyst"}},
		// === Service Mesh (4) ===
		{Name: "Istio", Category: "Service Mesh", Patterns: []string{"istio"}, Headers: map[string]string{"x-envoy-upstream-service-time": ""}},
		{Name: "Envoy", Category: "Service Mesh", Patterns: []string{"envoy"}, Headers: map[string]string{"server": "envoy"}},
		{Name: "Linkerd", Category: "Service Mesh", Patterns: []string{"linkerd"}, Headers: map[string]string{"l5d-success-class": ""}},
		{Name: "Consul Connect", Category: "Service Mesh", Patterns: []string{"consul"}},
		// === BaaS (12) ===
		{Name: "Firebase", Category: "BaaS", Patterns: []string{"firebaseapp.com", "firebase.google.com", "firebaseio.com"}, Scripts: []string{"firebase"}},
		{Name: "Supabase", Category: "BaaS", Patterns: []string{"supabase.co", "supabase.io"}, Scripts: []string{"supabase"}},
		{Name: "AWS Amplify", Category: "BaaS", Patterns: []string{"amplifyapp.com", "aws-amplify"}, Scripts: []string{"aws-amplify"}},
		{Name: "Appwrite", Category: "BaaS", Patterns: []string{"appwrite.io", "appwrite"}},
		{Name: "Parse", Category: "BaaS", Patterns: []string{"parseapi.com", "parse.com"}},
		{Name: "Back4App", Category: "BaaS", Patterns: []string{"back4app.com"}},
		{Name: "Hasura", Category: "BaaS", Patterns: []string{"hasura.io", "hasura.app"}},
		{Name: "Nhost", Category: "BaaS", Patterns: []string{"nhost.io", "nhost.run"}},
		{Name: "PocketBase", Category: "BaaS", Patterns: []string{"pocketbase"}},
		{Name: "Convex", Category: "BaaS", Patterns: []string{"convex.cloud", "convex.dev"}},
		{Name: "8base", Category: "BaaS", Patterns: []string{"8base.com"}},
		{Name: "Backendless", Category: "BaaS", Patterns: []string{"backendless.com"}},
		// === Headless CMS (8) ===
		{Name: "Sanity", Category: "Headless CMS", Patterns: []string{"sanity.io", "cdn.sanity.io"}},
		{Name: "Prismic", Category: "Headless CMS", Patterns: []string{"prismic.io", "cdn.prismic.io"}},
		{Name: "DatoCMS", Category: "Headless CMS", Patterns: []string{"datocms.com", "datocms-assets.com"}},
		{Name: "Hygraph (GraphCMS)", Category: "Headless CMS", Patterns: []string{"graphcms.com", "hygraph.com"}},
		{Name: "Storyblok", Category: "Headless CMS", Patterns: []string{"storyblok.com", "a.storyblok.com"}},
		{Name: "Directus", Category: "Headless CMS", Patterns: []string{"directus.io", "directus"}},
		{Name: "KeystoneJS", Category: "Headless CMS", Patterns: []string{"keystonejs.com", "keystone"}},
		{Name: "Payload CMS", Category: "Headless CMS", Patterns: []string{"payloadcms.com", "payload"}},
		// === Identity Provider (5) ===
		{Name: "Auth0", Category: "Identity Provider", Patterns: []string{"auth0.com", "cdn.auth0.com"}, Scripts: []string{"auth0"}},
		{Name: "Okta", Category: "Identity Provider", Patterns: []string{"okta.com", "oktacdn.com"}},
		{Name: "Keycloak", Category: "Identity Provider", Patterns: []string{"keycloak", "/auth/realms/"}},
		{Name: "AWS Cognito", Category: "Identity Provider", Patterns: []string{"cognito-idp", "amazoncognito.com"}},
		{Name: "Firebase Auth", Category: "Identity Provider", Patterns: []string{"firebaseauth", "identitytoolkit.googleapis.com"}},
		// === BI/Reporting (7) ===
		{Name: "Grafana", Category: "BI/Reporting", Patterns: []string{"grafana", "/grafana/"}, HTML: []string{"grafana-app"}},
		{Name: "Kibana", Category: "BI/Reporting", Patterns: []string{"kibana", "/kibana/"}},
		{Name: "Metabase", Category: "BI/Reporting", Patterns: []string{"metabase", "/metabase/"}},
		{Name: "Redash", Category: "BI/Reporting", Patterns: []string{"redash", "/redash/"}},
		{Name: "Apache Superset", Category: "BI/Reporting", Patterns: []string{"superset", "/superset/"}},
		{Name: "Tableau", Category: "BI/Reporting", Patterns: []string{"tableau.com", "tableausoftware.com"}},
		{Name: "Power BI", Category: "BI/Reporting", Patterns: []string{"powerbi.com", "pbix", "app.powerbi.com"}},
		// === GIS/Mapping (6) ===
		{Name: "Leaflet", Category: "GIS/Mapping", Patterns: []string{"leaflet", "L.map", "leafletjs"}, Scripts: []string{"leaflet.js"}},
		{Name: "Mapbox", Category: "GIS/Mapping", Patterns: []string{"mapbox.com", "mapboxgl"}, Scripts: []string{"api.mapbox.com"}},
		{Name: "Google Maps", Category: "GIS/Mapping", Patterns: []string{"maps.googleapis.com", "maps.google.com"}, Scripts: []string{"maps.googleapis.com/maps/api/js"}},
		{Name: "OpenLayers", Category: "GIS/Mapping", Patterns: []string{"openlayers", "ol.js"}, Scripts: []string{"openlayers"}},
		{Name: "ArcGIS", Category: "GIS/Mapping", Patterns: []string{"arcgis.com", "js.arcgis.com"}, Scripts: []string{"js.arcgis.com"}},
		{Name: "Cesium", Category: "GIS/Mapping", Patterns: []string{"cesium", "cesiumjs"}, Scripts: []string{"cesium.com"}},
		// === Media Streaming (6) ===
		{Name: "HLS.js", Category: "Media Streaming", Patterns: []string{"hls.js", ".m3u8"}, Scripts: []string{"hls.js"}},
		{Name: "DASH.js", Category: "Media Streaming", Patterns: []string{"dash.js", ".mpd"}, Scripts: []string{"dashjs"}},
		{Name: "Video.js", Category: "Media Streaming", Patterns: []string{"video.js", "videojs", "vjs-"}, Scripts: []string{"vjs.zencdn.net"}},
		{Name: "JW Player", Category: "Media Streaming", Patterns: []string{"jwplayer", "jwplatform"}, Scripts: []string{"cdn.jwplayer.com"}},
		{Name: "Wistia", Category: "Media Streaming", Patterns: []string{"wistia.com", "wistia-video"}, Scripts: []string{"fast.wistia.com"}},
		{Name: "Vimeo Player", Category: "Media Streaming", Patterns: []string{"player.vimeo.com"}, Scripts: []string{"player.vimeo.com"}},
		// === Notebook/ML (5) ===
		{Name: "Jupyter", Category: "Notebook/ML", Patterns: []string{"jupyter", "/jupyter/"}},
		{Name: "MLflow", Category: "Notebook/ML", Patterns: []string{"mlflow", "/mlflow/"}},
		{Name: "Apache Zeppelin", Category: "Notebook/ML", Patterns: []string{"zeppelin", "/zeppelin/"}},
		{Name: "TensorBoard", Category: "Notebook/ML", Patterns: []string{"tensorboard", "/tensorboard/"}},
		{Name: "Streamlit", Category: "Notebook/ML", Patterns: []string{"streamlit", "streamlitapp.com"}},
		// === CI/CD (12) ===
		{Name: "Jenkins", Category: "CI/CD", Patterns: []string{"jenkins", "/jenkins/"}, Headers: map[string]string{"x-jenkins": ""}},
		{Name: "GitLab CI", Category: "CI/CD", Patterns: []string{"gitlab", "/gitlab/"}},
		{Name: "GitHub Actions", Category: "CI/CD", Patterns: []string{"github.com/actions", "actions/checkout"}},
		{Name: "CircleCI", Category: "CI/CD", Patterns: []string{"circleci.com", "circleci"}},
		{Name: "Travis CI", Category: "CI/CD", Patterns: []string{"travis-ci.com", "travis-ci.org"}},
		{Name: "TeamCity", Category: "CI/CD", Patterns: []string{"teamcity", "/teamcity/"}},
		{Name: "Bamboo", Category: "CI/CD", Patterns: []string{"bamboo", "/bamboo/"}},
		{Name: "Azure DevOps", Category: "CI/CD", Patterns: []string{"dev.azure.com", "visualstudio.com"}},
		{Name: "Drone CI", Category: "CI/CD", Patterns: []string{"drone.io", "drone"}},
		{Name: "ArgoCD", Category: "CI/CD", Patterns: []string{"argocd", "/argocd/"}},
		{Name: "Spinnaker", Category: "CI/CD", Patterns: []string{"spinnaker", "/spinnaker/"}},
		{Name: "Buildkite", Category: "CI/CD", Patterns: []string{"buildkite.com", "buildkite"}},
		// === Task Scheduling (6) ===
		{Name: "Sidekiq", Category: "Task Scheduling", Patterns: []string{"sidekiq", "/sidekiq/"}},
		{Name: "Celery/Flower", Category: "Task Scheduling", Patterns: []string{"flower", "/flower/"}},
		{Name: "Apache Airflow", Category: "Task Scheduling", Patterns: []string{"airflow", "/airflow/"}},
		{Name: "Hangfire", Category: "Task Scheduling", Patterns: []string{"hangfire", "/hangfire/"}},
		{Name: "Bull/BullMQ", Category: "Task Scheduling", Patterns: []string{"bull-board", "/bull/"}},
		{Name: "Quartz Scheduler", Category: "Task Scheduling", Patterns: []string{"quartz", "org.quartz"}},
		// === Virtualization (5) ===
		{Name: "Proxmox VE", Category: "Virtualization", Patterns: []string{"proxmox", "/pve/"}},
		{Name: "VMware vSphere", Category: "Virtualization", Patterns: []string{"vsphere", "vmware"}},
		{Name: "oVirt", Category: "Virtualization", Patterns: []string{"ovirt", "/ovirt-engine/"}},
		{Name: "XenServer", Category: "Virtualization", Patterns: []string{"xenserver", "xencenter"}},
		{Name: "OpenStack", Category: "Virtualization", Patterns: []string{"openstack", "/dashboard/"}},
		// === SIEM (7) ===
		{Name: "Splunk", Category: "SIEM", Patterns: []string{"splunk", "/splunk/"}},
		{Name: "Elastic SIEM", Category: "SIEM", Patterns: []string{"elastic", "/kibana/app/siem"}},
		{Name: "QRadar", Category: "SIEM", Patterns: []string{"qradar"}},
		{Name: "AlienVault", Category: "SIEM", Patterns: []string{"alienvault", "otx.alienvault.com"}},
		{Name: "Wazuh", Category: "SIEM", Patterns: []string{"wazuh", "/wazuh/"}},
		{Name: "OSSEC", Category: "SIEM", Patterns: []string{"ossec"}},
		{Name: "Graylog", Category: "SIEM", Patterns: []string{"graylog", "/graylog/"}},
		// === Network Monitoring (9) ===
		{Name: "Nagios", Category: "Network Monitoring", Patterns: []string{"nagios", "/nagios/"}},
		{Name: "Zabbix", Category: "Network Monitoring", Patterns: []string{"zabbix", "/zabbix/"}},
		{Name: "PRTG", Category: "Network Monitoring", Patterns: []string{"prtg", "/prtg/"}},
		{Name: "Cacti", Category: "Network Monitoring", Patterns: []string{"cacti", "/cacti/"}},
		{Name: "LibreNMS", Category: "Network Monitoring", Patterns: []string{"librenms", "/librenms/"}},
		{Name: "Prometheus", Category: "Network Monitoring", Patterns: []string{"prometheus", "/prometheus/"}},
		{Name: "Datadog", Category: "Network Monitoring", Patterns: []string{"datadoghq.com", "dd-agent"}, Scripts: []string{"datadoghq.com"}},
		{Name: "New Relic", Category: "Network Monitoring", Patterns: []string{"newrelic.com", "NREUM"}, Scripts: []string{"js-agent.newrelic.com"}},
		{Name: "Dynatrace", Category: "Network Monitoring", Patterns: []string{"dynatrace.com", "dtrum"}, Scripts: []string{"js.dtrace.io"}},
		// === Managed WP Hosting (10) ===
		{Name: "WP Engine", Category: "Managed WP Hosting", Patterns: []string{"wpe.com", "wpengine.com"}, Headers: map[string]string{"x-powered-by": "WP Engine"}},
		{Name: "Kinsta", Category: "Managed WP Hosting", Patterns: []string{"kinsta.com", "kinsta.cloud"}, Headers: map[string]string{"x-kinsta-cache": ""}},
		{Name: "Flywheel", Category: "Managed WP Hosting", Patterns: []string{"flywheel", "getflywheel.com"}},
		{Name: "Pantheon", Category: "Managed WP Hosting", Patterns: []string{"pantheon.io"}, Headers: map[string]string{"x-pantheon-styx-hostname": ""}},
		{Name: "Pagely", Category: "Managed WP Hosting", Patterns: []string{"pagely.com"}},
		{Name: "Cloudways", Category: "Managed WP Hosting", Patterns: []string{"cloudways.com"}},
		{Name: "SiteGround", Category: "Managed WP Hosting", Patterns: []string{"siteground.com"}, Headers: map[string]string{"x-siteground-cache": ""}},
		{Name: "Bluehost", Category: "Managed WP Hosting", Patterns: []string{"bluehost.com"}},
		{Name: "GoDaddy Managed WP", Category: "Managed WP Hosting", Patterns: []string{"secureserver.net", "godaddy.com"}},
		{Name: "WordPress.com (Automattic)", Category: "Managed WP Hosting", Patterns: []string{"wordpress.com", "wp.com"}},
		// === Prerendering (3) ===
		{Name: "Prerender.io", Category: "Prerendering", Patterns: []string{"prerender.io"}, Headers: map[string]string{"x-prerender": ""}},
		{Name: "Rendertron", Category: "Prerendering", Patterns: []string{"rendertron"}},
		{Name: "Puppeteer SSR", Category: "Prerendering", Patterns: []string{"HeadlessChrome"}},
		// === Node Process Manager (3) ===
		{Name: "PM2", Category: "Node Process Mgr", Patterns: []string{"pm2", "keymetrics"}},
		{Name: "Forever", Category: "Node Process Mgr", Patterns: []string{"forever"}},
		{Name: "Phusion Passenger", Category: "Node Process Mgr", Patterns: []string{"passenger"}, Headers: map[string]string{"x-powered-by": "Phusion Passenger"}},
		// === Document Signing (5) ===
		{Name: "DocuSign", Category: "Document Signing", Patterns: []string{"docusign.com", "docusign.net"}},
		{Name: "Adobe Sign", Category: "Document Signing", Patterns: []string{"adobesign.com", "echosign.com"}},
		{Name: "HelloSign", Category: "Document Signing", Patterns: []string{"hellosign.com"}},
		{Name: "PandaDoc", Category: "Document Signing", Patterns: []string{"pandadoc.com"}},
		{Name: "SignNow", Category: "Document Signing", Patterns: []string{"signnow.com"}},
		// === Telemetry/CDP (4) ===
		{Name: "Segment", Category: "Telemetry/CDP", Patterns: []string{"segment.io", "segment.com", "cdn.segment.com"}, Scripts: []string{"cdn.segment.com/analytics.js"}},
		{Name: "mParticle", Category: "Telemetry/CDP", Patterns: []string{"mparticle.com"}, Scripts: []string{"jssdkcdns.mparticle.com"}},
		{Name: "Rudderstack", Category: "Telemetry/CDP", Patterns: []string{"rudderstack.com"}, Scripts: []string{"cdn.rudderlabs.com"}},
		{Name: "Tealium", Category: "Telemetry/CDP", Patterns: []string{"tealium.com", "tealiumiq.com"}, Scripts: []string{"tags.tiqcdn.com"}},
		// === Review Widget (5) ===
		{Name: "Trustpilot", Category: "Review Widget", Patterns: []string{"trustpilot.com"}, Scripts: []string{"widget.trustpilot.com"}},
		{Name: "Yotpo", Category: "Review Widget", Patterns: []string{"yotpo.com"}, Scripts: []string{"staticw2.yotpo.com"}},
		{Name: "Judge.me", Category: "Review Widget", Patterns: []string{"judge.me"}, Scripts: []string{"judge.me/widget"}},
		{Name: "Bazaarvoice", Category: "Review Widget", Patterns: []string{"bazaarvoice.com"}, Scripts: []string{"display.ugc.bazaarvoice.com"}},
		{Name: "Stamped.io", Category: "Review Widget", Patterns: []string{"stamped.io"}, Scripts: []string{"stamped.io/assets/widget"}},
		// === WebRTC (4) ===
		{Name: "Twilio", Category: "WebRTC", Patterns: []string{"twilio.com"}, Scripts: []string{"media.twiliocdn.com"}},
		{Name: "Agora", Category: "WebRTC", Patterns: []string{"agora.io"}, Scripts: []string{"download.agora.io"}},
		{Name: "Vonage (Nexmo)", Category: "WebRTC", Patterns: []string{"vonage.com", "nexmo.com", "tokbox.com"}},
		{Name: "Daily.co", Category: "WebRTC", Patterns: []string{"daily.co"}},
		// === Infrastructure as Code (6) ===
		{Name: "Terraform", Category: "Infrastructure as Code", Patterns: []string{"terraform", ".tf"}},
		{Name: "Ansible", Category: "Infrastructure as Code", Patterns: []string{"ansible", "ansible.cfg"}},
		{Name: "Puppet", Category: "Infrastructure as Code", Patterns: []string{"puppet", "puppetlabs"}},
		{Name: "Chef", Category: "Infrastructure as Code", Patterns: []string{"chef", "opscode"}},
		{Name: "Pulumi", Category: "Infrastructure as Code", Patterns: []string{"pulumi.com", "pulumi"}},
		{Name: "Helm", Category: "Infrastructure as Code", Patterns: []string{"helm.sh", "Chart.yaml"}},
		// === Backup Solution (4) ===
		{Name: "Veeam", Category: "Backup Solution", Patterns: []string{"veeam"}},
		{Name: "UpdraftPlus", Category: "Backup Solution", Patterns: []string{"updraftplus", "updraft"}},
		{Name: "BackupBuddy", Category: "Backup Solution", Patterns: []string{"backupbuddy"}},
		{Name: "Duplicati", Category: "Backup Solution", Patterns: []string{"duplicati"}},
		// === GraphQL Platform (4) ===
		{Name: "Apollo GraphQL", Category: "GraphQL Platform", Patterns: []string{"apollo", "apollographql"}, Scripts: []string{"apollo-client"}},
		{Name: "Relay", Category: "GraphQL Platform", Patterns: []string{"relay", "react-relay"}},
		{Name: "Hasura", Category: "GraphQL Platform", Patterns: []string{"hasura", "hasura.io"}},
		{Name: "AWS AppSync", Category: "GraphQL Platform", Patterns: []string{"appsync", "amazonaws.com/graphql"}},
		// === Minification (5) ===
		{Name: "Terser", Category: "Minification", Patterns: []string{"terser"}},
		{Name: "UglifyJS", Category: "Minification", Patterns: []string{"uglifyjs", "uglify"}},
		{Name: "esbuild", Category: "Minification", Patterns: []string{"esbuild"}},
		{Name: "SWC", Category: "Minification", Patterns: []string{"@swc/core", "swc"}},
		{Name: "cssnano", Category: "Minification", Patterns: []string{"cssnano"}},
		// === DBaaS (6) ===
		{Name: "PlanetScale", Category: "DBaaS", Patterns: []string{"planetscale.com", "planetscale"}},
		{Name: "Neon", Category: "DBaaS", Patterns: []string{"neon.tech"}},
		{Name: "CockroachDB Cloud", Category: "DBaaS", Patterns: []string{"cockroachlabs.com", "cockroachlabs.cloud"}},
		{Name: "MongoDB Atlas", Category: "DBaaS", Patterns: []string{"mongodb.net", "cloud.mongodb.com"}},
		{Name: "Upstash", Category: "DBaaS", Patterns: []string{"upstash.io", "upstash.com"}},
		{Name: "Turso", Category: "DBaaS", Patterns: []string{"turso.tech"}},
		// === E-Learning (6) ===
		{Name: "Moodle", Category: "E-Learning", Patterns: []string{"moodle", "/moodle/"}, Meta: map[string]string{"generator": "Moodle"}},
		{Name: "Canvas LMS", Category: "E-Learning", Patterns: []string{"instructure.com", "canvas"}},
		{Name: "Blackboard", Category: "E-Learning", Patterns: []string{"blackboard.com", "blackboard"}},
		{Name: "Open edX", Category: "E-Learning", Patterns: []string{"edx.org", "openedx"}},
		{Name: "Teachable", Category: "E-Learning", Patterns: []string{"teachable.com", "teachablecdn.com"}},
		{Name: "Thinkific", Category: "E-Learning", Patterns: []string{"thinkific.com"}},
		// === Government/Civic (6) ===
		{Name: "Drupal (Gov)", Category: "Government/Civic", Patterns: []string{"uswds", "usa-"}, HTML: []string{"usa-banner"}},
		{Name: "CKAN", Category: "Government/Civic", Patterns: []string{"ckan", "/ckan/"}},
		{Name: "OpenCMS", Category: "Government/Civic", Patterns: []string{"opencms", "/opencms/"}},
		{Name: "Liferay", Category: "Government/Civic", Patterns: []string{"liferay", "/liferay/"}, Headers: map[string]string{"liferay-portal": ""}},
		{Name: "Plone", Category: "Government/Civic", Patterns: []string{"plone", "/plone/"}},
		{Name: "eZ Platform", Category: "Government/Civic", Patterns: []string{"ezplatform", "ezpublish"}},
		// === Payment Gateway (5) ===
		{Name: "Stripe", Category: "Payment Gateway", Patterns: []string{"stripe.com", "js.stripe.com"}, Scripts: []string{"js.stripe.com/v3"}},
		{Name: "PayPal", Category: "Payment Gateway", Patterns: []string{"paypal.com", "paypalobjects.com"}, Scripts: []string{"paypal.com/sdk/js"}},
		{Name: "Braintree", Category: "Payment Gateway", Patterns: []string{"braintreegateway.com", "braintree-api"}, Scripts: []string{"js.braintreegateway.com"}},
		{Name: "Square", Category: "Payment Gateway", Patterns: []string{"squareup.com", "square.com"}, Scripts: []string{"js.squareup.com"}},
		{Name: "Adyen", Category: "Payment Gateway", Patterns: []string{"adyen.com"}, Scripts: []string{"checkoutshopper-live.adyen.com"}},
	}
}

// ===========================================================================
// DNS MODULE — stdlib only (net.Lookup*)
// ===========================================================================

func odintDNSEnumerate(ctx context.Context, domain string) ([]OdintDNSRecord, error) {
	var records []OdintDNSRecord
	resolver := &net.Resolver{}

	// A / AAAA records
	addrs, err := resolver.LookupHost(ctx, domain)
	if err == nil {
		for _, addr := range addrs {
			if strings.Contains(addr, ":") {
				records = append(records, OdintDNSRecord{Type: "AAAA", Value: addr})
			} else {
				records = append(records, OdintDNSRecord{Type: "A", Value: addr})
			}
		}
	}

	// MX records
	mxs, err := resolver.LookupMX(ctx, domain)
	if err == nil {
		for _, mx := range mxs {
			records = append(records, OdintDNSRecord{Type: "MX", Value: fmt.Sprintf("%d %s", mx.Pref, mx.Host)})
		}
	}

	// NS records
	nss, err := resolver.LookupNS(ctx, domain)
	if err == nil {
		for _, ns := range nss {
			records = append(records, OdintDNSRecord{Type: "NS", Value: ns.Host})
		}
	}

	// TXT records
	txts, err := resolver.LookupTXT(ctx, domain)
	if err == nil {
		for _, txt := range txts {
			records = append(records, OdintDNSRecord{Type: "TXT", Value: txt})
		}
	}

	// CNAME
	cname, err := resolver.LookupCNAME(ctx, domain)
	if err == nil && cname != "" && cname != domain+"." {
		records = append(records, OdintDNSRecord{Type: "CNAME", Value: cname})
	}

	// PTR — reverse lookup for each A record
	for _, addr := range addrs {
		if !strings.Contains(addr, ":") {
			ptrs, err := resolver.LookupAddr(ctx, addr)
			if err == nil {
				for _, ptr := range ptrs {
					records = append(records, OdintDNSRecord{Type: "PTR", Value: fmt.Sprintf("%s -> %s", addr, ptr)})
				}
			}
		}
	}

	// SPF — already in TXT, but flag it
	for _, r := range records {
		if r.Type == "TXT" && strings.HasPrefix(r.Value, "v=spf1") {
			records = append(records, OdintDNSRecord{Type: "SPF", Value: r.Value})
		}
	}

	// DMARC
	dmarcTxts, err := resolver.LookupTXT(ctx, "_dmarc."+domain)
	if err == nil {
		for _, txt := range dmarcTxts {
			if strings.HasPrefix(txt, "v=DMARC1") {
				records = append(records, OdintDNSRecord{Type: "DMARC", Value: txt})
			}
		}
	}

	return records, nil
}

func (tui *TUI) odintStoreDNS(scanID int64, domain string, records []OdintDNSRecord) {
	for _, r := range records {
		tui.db.conn.Exec(`
			INSERT OR IGNORE INTO odint_dns (scan_id, domain, record_type, record_value, ttl, discovered)
			VALUES (?, ?, ?, ?, 0, CURRENT_TIMESTAMP)`,
			scanID, domain, r.Type, r.Value)
	}
}

// ===========================================================================
// SSL MODULE — crypto/tls + crypto/x509
// ===========================================================================

func odintSSLAnalyze(ctx context.Context, domain string, timeout time.Duration) (
	issuer, subject, serial string,
	notBefore, notAfter time.Time,
	sigAlg string, keySize int,
	sanNames []string, isExpired bool,
	tlsVersions []string, err error,
) {
	dialer := &net.Dialer{Timeout: timeout}
	host := domain
	if !strings.Contains(host, ":") {
		host = domain + ":443"
	}

	conn, dialErr := tls.DialWithDialer(dialer, "tcp", host, &tls.Config{
		InsecureSkipVerify: true,
	})
	if dialErr != nil {
		err = dialErr
		return
	}
	defer conn.Close()

	state := conn.ConnectionState()
	if len(state.PeerCertificates) == 0 {
		err = fmt.Errorf("no certificates presented")
		return
	}

	cert := state.PeerCertificates[0]
	issuer = cert.Issuer.String()
	subject = cert.Subject.String()
	serial = cert.SerialNumber.String()
	notBefore = cert.NotBefore
	notAfter = cert.NotAfter
	sigAlg = cert.SignatureAlgorithm.String()
	isExpired = time.Now().After(cert.NotAfter)

	// Key size
	switch pub := cert.PublicKey.(type) {
	case interface{ Size() int }:
		keySize = pub.Size() * 8
	default:
		_ = pub
		keySize = 0
	}

	// SAN names
	sanNames = append(sanNames, cert.DNSNames...)
	for _, ip := range cert.IPAddresses {
		sanNames = append(sanNames, ip.String())
	}

	// Check TLS version support
	tlsConfigs := []struct {
		version uint16
		name    string
	}{
		{tls.VersionTLS10, "TLS 1.0"},
		{tls.VersionTLS11, "TLS 1.1"},
		{tls.VersionTLS12, "TLS 1.2"},
		{tls.VersionTLS13, "TLS 1.3"},
	}

	for _, tc := range tlsConfigs {
		select {
		case <-ctx.Done():
			return
		default:
		}
		testConn, testErr := tls.DialWithDialer(
			&net.Dialer{Timeout: 3 * time.Second},
			"tcp", host,
			&tls.Config{
				InsecureSkipVerify: true,
				MinVersion:         tc.version,
				MaxVersion:         tc.version,
			},
		)
		if testErr == nil {
			tlsVersions = append(tlsVersions, tc.name)
			testConn.Close()
		}
	}

	return
}

func (tui *TUI) odintStoreSSL(scanID int64, domain string,
	issuer, subject, serial string,
	notBefore, notAfter time.Time,
	sigAlg string, keySize int,
	sanNames []string, isExpired bool,
) {
	expired := 0
	if isExpired {
		expired = 1
	}
	sanStr := strings.Join(sanNames, ", ")
	tui.db.conn.Exec(`
		INSERT OR IGNORE INTO odint_ssl (scan_id, domain, issuer, subject, serial, not_before, not_after, sig_algorithm, key_size, san_names, is_expired, discovered)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
		scanID, domain, issuer, subject, serial,
		notBefore.Format("2006-01-02 15:04:05"),
		notAfter.Format("2006-01-02 15:04:05"),
		sigAlg, keySize, sanStr, expired)
}

// ===========================================================================
// TECHNOLOGY FINGERPRINTING
// ===========================================================================

func odintDetectTechnologies(headers map[string]string, cookies []string, body string, signatures []OdintTechSignature) []OdintDetectedTech {
	seen := make(map[string]bool)
	var results []OdintDetectedTech

	bodyLower := strings.ToLower(body)

	for _, sig := range signatures {
		matched := false
		source := ""

		// Check headers
		for hdrKey, hdrVal := range sig.Headers {
			if actual, ok := headers[hdrKey]; ok {
				if hdrVal == "" || strings.Contains(strings.ToLower(actual), strings.ToLower(hdrVal)) {
					matched = true
					source = "header:" + hdrKey
					break
				}
			}
			// Case-insensitive header lookup
			for k, v := range headers {
				if strings.EqualFold(k, hdrKey) {
					if hdrVal == "" || strings.Contains(strings.ToLower(v), strings.ToLower(hdrVal)) {
						matched = true
						source = "header:" + k
						break
					}
				}
			}
			if matched {
				break
			}
		}

		// Check cookies
		if !matched {
			for _, sigCookie := range sig.Cookies {
				for _, cookie := range cookies {
					if strings.Contains(strings.ToLower(cookie), strings.ToLower(sigCookie)) {
						matched = true
						source = "cookie:" + sigCookie
						break
					}
				}
				if matched {
					break
				}
			}
		}

		// Check body patterns
		if !matched {
			for _, pattern := range sig.Patterns {
				if strings.Contains(bodyLower, strings.ToLower(pattern)) {
					matched = true
					source = "body:" + pattern
					break
				}
			}
		}

		// Check scripts
		if !matched {
			for _, script := range sig.Scripts {
				if strings.Contains(bodyLower, strings.ToLower(script)) {
					matched = true
					source = "script:" + script
					break
				}
			}
		}

		// Check HTML patterns
		if !matched {
			for _, html := range sig.HTML {
				if strings.Contains(bodyLower, strings.ToLower(html)) {
					matched = true
					source = "html:" + html
					break
				}
			}
		}

		// Check meta generator
		if !matched {
			for metaName, metaVal := range sig.Meta {
				if metaName == "generator" {
					// Check against the body for meta generator tags
					genLower := strings.ToLower(metaVal)
					if strings.Contains(bodyLower, "generator\" content=\""+genLower) ||
						strings.Contains(bodyLower, "generator\" content='"+genLower) {
						matched = true
						source = "meta:generator"
						break
					}
				}
			}
		}

		if matched && !seen[sig.Name] {
			seen[sig.Name] = true
			results = append(results, OdintDetectedTech{
				Name:     sig.Name,
				Category: sig.Category,
				Source:   source,
			})
		}
	}

	return results
}

func (tui *TUI) odintStoreTech(scanID int64, domain string, techs []OdintDetectedTech) {
	for _, t := range techs {
		tui.db.conn.Exec(`
			INSERT OR IGNORE INTO odint_technologies (scan_id, domain, tech_name, tech_category, tech_version, confidence, discovered)
			VALUES (?, ?, ?, ?, '', 80, CURRENT_TIMESTAMP)`,
			scanID, domain, t.Name, t.Category)
	}
}

// ===========================================================================
// HTTP ANALYSIS
// ===========================================================================

func odintAnalyzeHTTP(resp *http.Response, body string) (title string, securityHeaders map[string]string, findings []string) {
	securityHeaders = make(map[string]string)

	// Extract title
	titleRe := regexp.MustCompile(`(?i)<title[^>]*>(.*?)</title>`)
	if m := titleRe.FindStringSubmatch(body); len(m) > 1 {
		title = strings.TrimSpace(m[1])
		if len(title) > 200 {
			title = title[:200]
		}
	}

	// Security headers to check
	secHeaders := []struct {
		name     string
		required bool
	}{
		{"X-Frame-Options", true},
		{"Content-Security-Policy", true},
		{"X-XSS-Protection", false},
		{"Strict-Transport-Security", true},
		{"X-Content-Type-Options", true},
		{"Referrer-Policy", true},
		{"Permissions-Policy", true},
	}

	for _, sh := range secHeaders {
		val := resp.Header.Get(sh.name)
		if val != "" {
			securityHeaders[sh.name] = val
		} else if sh.required {
			findings = append(findings, fmt.Sprintf("Missing security header: %s", sh.name))
		}
	}

	// Check for information disclosure
	serverHeader := resp.Header.Get("Server")
	if serverHeader != "" {
		securityHeaders["Server"] = serverHeader
		// Version disclosure
		if matched, _ := regexp.MatchString(`\d+\.\d+`, serverHeader); matched {
			findings = append(findings, fmt.Sprintf("Server version disclosure: %s", serverHeader))
		}
	}

	xPowered := resp.Header.Get("X-Powered-By")
	if xPowered != "" {
		securityHeaders["X-Powered-By"] = xPowered
		findings = append(findings, fmt.Sprintf("Technology disclosure via X-Powered-By: %s", xPowered))
	}

	return
}

// ===========================================================================
// CONTENT EXTRACTION
// ===========================================================================

func odintExtractContent(body string) (emails []string, phones []string, socialLinks []string) {
	// Email extraction
	emailRe := regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
	emailMatches := emailRe.FindAllString(body, -1)
	seen := make(map[string]bool)
	for _, e := range emailMatches {
		lower := strings.ToLower(e)
		// Skip common false positives
		if strings.HasSuffix(lower, ".png") || strings.HasSuffix(lower, ".jpg") ||
			strings.HasSuffix(lower, ".gif") || strings.HasSuffix(lower, ".svg") ||
			strings.HasSuffix(lower, ".css") || strings.HasSuffix(lower, ".js") ||
			strings.Contains(lower, "example.com") || strings.Contains(lower, "sentry") ||
			strings.Contains(lower, "webpack") {
			continue
		}
		if !seen[lower] {
			seen[lower] = true
			emails = append(emails, lower)
		}
	}

	// Phone extraction
	phoneRe := regexp.MustCompile(`(?:(?:\+?1[\s.-]?)?(?:\(?\d{3}\)?[\s.-]?)\d{3}[\s.-]?\d{4})`)
	phoneMatches := phoneRe.FindAllString(body, -1)
	phoneSeen := make(map[string]bool)
	for _, p := range phoneMatches {
		cleaned := strings.Map(func(r rune) rune {
			if r >= '0' && r <= '9' || r == '+' {
				return r
			}
			return -1
		}, p)
		if len(cleaned) >= 10 && !phoneSeen[cleaned] {
			phoneSeen[cleaned] = true
			phones = append(phones, p)
		}
	}

	// Social media links
	socialPatterns := []string{
		`https?://(?:www\.)?facebook\.com/[a-zA-Z0-9._\-]+`,
		`https?://(?:www\.)?twitter\.com/[a-zA-Z0-9_]+`,
		`https?://(?:www\.)?x\.com/[a-zA-Z0-9_]+`,
		`https?://(?:www\.)?linkedin\.com/(?:in|company)/[a-zA-Z0-9_\-]+`,
		`https?://(?:www\.)?instagram\.com/[a-zA-Z0-9._]+`,
		`https?://(?:www\.)?github\.com/[a-zA-Z0-9_\-]+`,
		`https?://(?:www\.)?youtube\.com/(?:channel|c|user|@)[a-zA-Z0-9_\-/]+`,
		`https?://(?:www\.)?tiktok\.com/@[a-zA-Z0-9._]+`,
		`https?://(?:www\.)?pinterest\.com/[a-zA-Z0-9_]+`,
		`https?://(?:www\.)?reddit\.com/(?:r|u|user)/[a-zA-Z0-9_]+`,
		`https?://(?:www\.)?discord\.gg/[a-zA-Z0-9]+`,
		`https?://(?:www\.)?t\.me/[a-zA-Z0-9_]+`,
		`https?://(?:www\.)?mastodon\.[a-z]+/@[a-zA-Z0-9_]+`,
	}
	socialSeen := make(map[string]bool)
	for _, pattern := range socialPatterns {
		re := regexp.MustCompile(pattern)
		matches := re.FindAllString(body, -1)
		for _, m := range matches {
			lower := strings.ToLower(m)
			if !socialSeen[lower] {
				socialSeen[lower] = true
				socialLinks = append(socialLinks, m)
			}
		}
	}

	return
}

// ===========================================================================
// WHOIS — stdlib raw TCP to whois servers
// ===========================================================================

func odintWhoisLookup(ctx context.Context, domain string) (registrar, createdDate, expiryDate, nameServers, registrant, rawData string, err error) {
	// Step 1: query whois.iana.org for referral
	referralServer := "whois.iana.org"
	raw, queryErr := odintWhoisQuery(ctx, referralServer, domain)
	if queryErr != nil {
		err = queryErr
		return
	}

	// Find referral whois server
	referral := ""
	for _, line := range strings.Split(raw, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(strings.ToLower(line), "refer:") || strings.HasPrefix(strings.ToLower(line), "whois:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				referral = strings.TrimSpace(parts[1])
				break
			}
		}
	}

	// Step 2: query referral server
	if referral != "" {
		raw2, queryErr2 := odintWhoisQuery(ctx, referral, domain)
		if queryErr2 == nil {
			raw = raw2
		}
	}

	rawData = raw

	// Parse fields from raw text
	for _, line := range strings.Split(raw, "\n") {
		line = strings.TrimSpace(line)
		lower := strings.ToLower(line)

		if registrar == "" && (strings.HasPrefix(lower, "registrar:") || strings.HasPrefix(lower, "registrar name:")) {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				registrar = strings.TrimSpace(parts[1])
			}
		}
		if createdDate == "" && (strings.HasPrefix(lower, "creation date:") || strings.HasPrefix(lower, "created:") || strings.HasPrefix(lower, "registered:") || strings.HasPrefix(lower, "registered on:")) {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				createdDate = strings.TrimSpace(parts[1])
			}
		}
		if expiryDate == "" && (strings.HasPrefix(lower, "registry expiry date:") || strings.HasPrefix(lower, "expiry date:") || strings.HasPrefix(lower, "expiration date:") || strings.HasPrefix(lower, "expires:") || strings.HasPrefix(lower, "paid-till:")) {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				expiryDate = strings.TrimSpace(parts[1])
			}
		}
		if strings.HasPrefix(lower, "name server:") || strings.HasPrefix(lower, "nserver:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				ns := strings.TrimSpace(parts[1])
				if nameServers != "" {
					nameServers += ", "
				}
				nameServers += ns
			}
		}
		if registrant == "" && (strings.HasPrefix(lower, "registrant organization:") || strings.HasPrefix(lower, "registrant:") || strings.HasPrefix(lower, "org:")) {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				registrant = strings.TrimSpace(parts[1])
			}
		}
	}

	return
}

func odintWhoisQuery(ctx context.Context, server, domain string) (string, error) {
	addr := server + ":43"
	dialer := &net.Dialer{Timeout: 10 * time.Second}
	conn, err := dialer.DialContext(ctx, "tcp", addr)
	if err != nil {
		return "", fmt.Errorf("whois connect %s: %v", server, err)
	}
	defer conn.Close()
	conn.SetDeadline(time.Now().Add(15 * time.Second))

	_, err = conn.Write([]byte(domain + "\r\n"))
	if err != nil {
		return "", fmt.Errorf("whois write: %v", err)
	}

	data, err := io.ReadAll(conn)
	if err != nil {
		return "", fmt.Errorf("whois read: %v", err)
	}

	return string(data), nil
}

func (tui *TUI) odintStoreWhois(domain, registrar, createdDate, expiryDate, nameServers, registrant, rawData string) {
	// Truncate raw data if too large
	if len(rawData) > 10000 {
		rawData = rawData[:10000]
	}
	tui.db.conn.Exec(`
		INSERT INTO odint_whois (domain, registrar, created_date, expiry_date, name_servers, registrant, raw_data, discovered)
		VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
		ON CONFLICT(domain) DO UPDATE SET
			registrar = excluded.registrar,
			created_date = excluded.created_date,
			expiry_date = excluded.expiry_date,
			name_servers = excluded.name_servers,
			registrant = excluded.registrant,
			raw_data = excluded.raw_data,
			discovered = CURRENT_TIMESTAMP`,
		domain, registrar, createdDate, expiryDate, nameServers, registrant, rawData)
}

// ===========================================================================
// WAYBACK MACHINE
// ===========================================================================

func odintWaybackLookup(ctx context.Context, client *http.Client, domain string, limit int) ([]OdintWaybackSnapshot, error) {
	if limit <= 0 {
		limit = 100
	}

	apiURL := fmt.Sprintf("https://web.archive.org/cdx/search/cdx?url=%s/*&output=json&limit=%d&fl=timestamp,original,mimetype,statuscode&collapse=urlkey",
		url.QueryEscape(domain), limit)

	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("wayback API: %v", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("wayback read: %v", err)
	}

	var rows [][]string
	if err := json.Unmarshal(body, &rows); err != nil {
		return nil, fmt.Errorf("wayback parse: %v", err)
	}

	var snapshots []OdintWaybackSnapshot
	// First row is header, skip it
	for i, row := range rows {
		if i == 0 {
			continue
		}
		if len(row) >= 4 {
			snapshots = append(snapshots, OdintWaybackSnapshot{
				Timestamp:  row[0],
				URL:        row[1],
				MimeType:   row[2],
				StatusCode: row[3],
			})
		}
	}

	return snapshots, nil
}

func (tui *TUI) odintStoreWayback(domain string, snapshots []OdintWaybackSnapshot) {
	for _, s := range snapshots {
		sc, _ := strconv.Atoi(s.StatusCode)
		tui.db.conn.Exec(`
			INSERT OR IGNORE INTO odint_wayback (domain, url, timestamp, status_code, mime_type, discovered)
			VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, s.URL, s.Timestamp, sc, s.MimeType)
	}
}

// ===========================================================================
// SHODAN API (optional, requires API key)
// ===========================================================================

func odintQueryShodan(ctx context.Context, client *http.Client, target string, apiKey string) (ports []int, osName string, org string, err error) {
	if apiKey == "" {
		err = fmt.Errorf("no Shodan API key configured")
		return
	}

	// Resolve to IP first
	ips, lookupErr := net.DefaultResolver.LookupHost(ctx, target)
	if lookupErr != nil || len(ips) == 0 {
		err = fmt.Errorf("could not resolve %s", target)
		return
	}
	ip := ips[0]

	apiURL := fmt.Sprintf("https://api.shodan.io/shodan/host/%s?key=%s", ip, apiKey)
	req, reqErr := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if reqErr != nil {
		err = reqErr
		return
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, respErr := client.Do(req)
	if respErr != nil {
		err = fmt.Errorf("shodan API: %v", respErr)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	var result struct {
		Ports []int  `json:"ports"`
		OS    string `json:"os"`
		Org   string `json:"org"`
	}
	if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
		err = fmt.Errorf("shodan parse: %v", jsonErr)
		return
	}

	ports = result.Ports
	osName = result.OS
	org = result.Org
	return
}

// ===========================================================================
// HTTP CLIENT FACTORY
// ===========================================================================

func (tui *TUI) odintCreateHTTPClient() *http.Client {
	transport := &http.Transport{
		TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
		MaxIdleConns:          100,
		MaxIdleConnsPerHost:   10,
		MaxConnsPerHost:       10,
		IdleConnTimeout:       90 * time.Second,
		DisableKeepAlives:     false,
		ResponseHeaderTimeout: tui.odintTimeout,
		TLSHandshakeTimeout:   5 * time.Second,
	}
	return &http.Client{
		Timeout:   tui.odintTimeout,
		Transport: transport,
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			if len(via) >= 10 {
				return fmt.Errorf("too many redirects")
			}
			return nil
		},
	}
}

// ===========================================================================
// SCANNER ORCHESTRATION — Full Recon
// ===========================================================================

func (tui *TUI) runOdintFullRecon(ctx context.Context, domain string) {
	tui.startLog("full-recon")
	defer tui.closeLog()

	tui.odintCurrentDomain = domain
	tui.odintModulesRun = ""
	atomic.StoreInt64(&tui.odintProgress, 0)
	atomic.StoreInt64(&tui.odintTotal, 42) // 42 modules in v4.0.0+

	baseURL := "https://" + domain

	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("[*] === ODINT Full Recon: %s ===", domain))
	tui.writeLog("Starting full recon on %s", domain)

	// Create scan record
	res, err := tui.db.conn.Exec(`
		INSERT INTO odint_scans (domain, scan_type, started, status, modules_run, findings_count)
		VALUES (?, 'full', CURRENT_TIMESTAMP, 'running', '', 0)`, domain)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] DB error: %v", err))
		return
	}
	scanID, _ := res.LastInsertId()

	client := tui.odintCreateHTTPClient()
	totalFindings := 0
	var modulesRun []string

	// Module 1: DNS
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [1/7] DNS Enumeration...")
	tui.writeLog("Module 1: DNS enumeration")
	dnsRecords, dnsErr := odintDNSEnumerate(ctx, domain)
	if dnsErr != nil {
		tui.addOutput(fmt.Sprintf("[-] DNS error: %v", dnsErr))
		tui.writeLog("DNS error: %v", dnsErr)
	} else {
		tui.odintStoreDNS(scanID, domain, dnsRecords)
		for _, r := range dnsRecords {
			tui.addOutput(fmt.Sprintf("    [DNS] %s: %s", r.Type, r.Value))
		}
		tui.addOutput(fmt.Sprintf("[+] DNS: %d records found", len(dnsRecords)))
		tui.writeLog("DNS: %d records", len(dnsRecords))
	}
	modulesRun = append(modulesRun, "DNS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Module 2: SSL/TLS
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [2/7] SSL/TLS Analysis...")
	tui.writeLog("Module 2: SSL/TLS analysis")
	issuer, subject, serial, notBefore, notAfter, sigAlg, keySize, sanNames, isExpired, tlsVersions, sslErr := odintSSLAnalyze(ctx, domain, tui.odintTimeout)
	if sslErr != nil {
		tui.addOutput(fmt.Sprintf("[-] SSL error: %v", sslErr))
		tui.writeLog("SSL error: %v", sslErr)
	} else {
		tui.odintStoreSSL(scanID, domain, issuer, subject, serial, notBefore, notAfter, sigAlg, keySize, sanNames, isExpired)
		tui.addOutput(fmt.Sprintf("    [SSL] Issuer: %s", issuer))
		tui.addOutput(fmt.Sprintf("    [SSL] Subject: %s", subject))
		tui.addOutput(fmt.Sprintf("    [SSL] Valid: %s to %s", notBefore.Format("2006-01-02"), notAfter.Format("2006-01-02")))
		tui.addOutput(fmt.Sprintf("    [SSL] Algorithm: %s | Key: %d bits", sigAlg, keySize))
		if isExpired {
			tui.addOutput("[!] SSL CERTIFICATE IS EXPIRED")
			tui.db.conn.Exec(`INSERT INTO odint_findings (scan_id, domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, ?, 'ssl', 'Expired SSL Certificate', 'Certificate expired on ' || ?, 'high', 'SSL', CURRENT_TIMESTAMP)`,
				scanID, domain, notAfter.Format("2006-01-02"))
			totalFindings++
		}
		if len(sanNames) > 0 {
			tui.addOutput(fmt.Sprintf("    [SSL] SANs: %s", strings.Join(sanNames, ", ")))
		}
		if len(tlsVersions) > 0 {
			tui.addOutput(fmt.Sprintf("    [SSL] TLS versions: %s", strings.Join(tlsVersions, ", ")))
			// Check for old TLS
			for _, v := range tlsVersions {
				if v == "TLS 1.0" || v == "TLS 1.1" {
					tui.addOutput(fmt.Sprintf("[!] Deprecated TLS version supported: %s", v))
					tui.db.conn.Exec(`INSERT INTO odint_findings (scan_id, domain, finding_type, title, detail, severity, source_module, discovered)
						VALUES (?, ?, 'ssl', 'Deprecated TLS Version', ?, 'medium', 'SSL', CURRENT_TIMESTAMP)`,
						scanID, domain, v+" is supported")
					totalFindings++
				}
			}
		}
	}
	modulesRun = append(modulesRun, "SSL")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Module 3: HTTP Analysis + Tech Detection + Content Extraction
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [3/7] HTTP Analysis...")
	tui.writeLog("Module 3: HTTP analysis")

	targetURL := "https://" + domain
	req, reqErr := http.NewRequestWithContext(ctx, "GET", targetURL, nil)
	var respBody string
	var httpHeaders map[string]string
	var httpCookies []string
	var savedBody string
	var savedCookies []*http.Cookie
	var savedHeaders http.Header

	if reqErr == nil {
		req.Header.Set("User-Agent", UserAgent)
		resp, respErr := client.Do(req)
		if respErr != nil {
			// Try HTTP
			targetURL = "http://" + domain
			req, _ = http.NewRequestWithContext(ctx, "GET", targetURL, nil)
			req.Header.Set("User-Agent", UserAgent)
			resp, respErr = client.Do(req)
		}
		if respErr == nil {
			defer resp.Body.Close()
			bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024)) // 2MB limit
			respBody = string(bodyBytes)
			savedBody = respBody
			savedCookies = resp.Cookies()
			savedHeaders = resp.Header

			// Collect headers
			httpHeaders = make(map[string]string)
			for k, vals := range resp.Header {
				httpHeaders[k] = strings.Join(vals, ", ")
			}

			// Collect cookies
			for _, c := range savedCookies {
				httpCookies = append(httpCookies, c.Name+"="+c.Value)
			}

			pageTitle, secHeaders, httpFindings := odintAnalyzeHTTP(resp, respBody)
			if pageTitle != "" {
				tui.addOutput(fmt.Sprintf("    [HTTP] Title: %s", pageTitle))
			}
			tui.addOutput(fmt.Sprintf("    [HTTP] Status: %d | Headers: %d", resp.StatusCode, len(httpHeaders)))

			for hdr, val := range secHeaders {
				tui.addOutput(fmt.Sprintf("    [HTTP] %s: %s", hdr, val))
			}

			for _, finding := range httpFindings {
				tui.addOutput(fmt.Sprintf("[!] %s", finding))
				tui.db.conn.Exec(`INSERT INTO odint_findings (scan_id, domain, finding_type, title, detail, severity, source_module, discovered)
					VALUES (?, ?, 'http', ?, '', 'info', 'HTTP', CURRENT_TIMESTAMP)`,
					scanID, domain, finding)
				totalFindings++
			}
		} else {
			tui.addOutput(fmt.Sprintf("[-] HTTP error: %v", respErr))
		}
	}
	modulesRun = append(modulesRun, "HTTP")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Module 4: Technology Fingerprinting
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [4/7] Technology Detection...")
	tui.writeLog("Module 4: Tech detection")
	signatures := odintLoadSignatures()
	if httpHeaders == nil {
		httpHeaders = make(map[string]string)
	}
	techs := odintDetectTechnologies(httpHeaders, httpCookies, respBody, signatures)
	tui.odintStoreTech(scanID, domain, techs)
	if len(techs) > 0 {
		// Group by category
		categories := make(map[string][]string)
		for _, t := range techs {
			categories[t.Category] = append(categories[t.Category], t.Name)
		}
		catKeys := make([]string, 0, len(categories))
		for k := range categories {
			catKeys = append(catKeys, k)
		}
		sort.Strings(catKeys)
		for _, cat := range catKeys {
			tui.addOutput(fmt.Sprintf("    [TECH] %s: %s", cat, strings.Join(categories[cat], ", ")))
		}
		tui.addOutput(fmt.Sprintf("[+] Tech: %d technologies detected across %d categories", len(techs), len(categories)))
	} else {
		tui.addOutput("    [TECH] No technologies detected")
	}
	modulesRun = append(modulesRun, "TECH")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Module 5: Content Extraction
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [5/7] Content Extraction...")
	tui.writeLog("Module 5: Content extraction")
	emails, phones, socials := odintExtractContent(respBody)
	if len(emails) > 0 {
		for _, e := range emails {
			tui.addOutput(fmt.Sprintf("    [EMAIL] %s", e))
			tui.db.conn.Exec(`INSERT INTO odint_findings (scan_id, domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, ?, 'email', 'Email Found', ?, 'info', 'Content', CURRENT_TIMESTAMP)`,
				scanID, domain, e)
			totalFindings++
		}
	}
	if len(phones) > 0 {
		for _, p := range phones {
			tui.addOutput(fmt.Sprintf("    [PHONE] %s", p))
			tui.db.conn.Exec(`INSERT INTO odint_findings (scan_id, domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, ?, 'phone', 'Phone Number Found', ?, 'info', 'Content', CURRENT_TIMESTAMP)`,
				scanID, domain, p)
			totalFindings++
		}
	}
	if len(socials) > 0 {
		for _, s := range socials {
			tui.addOutput(fmt.Sprintf("    [SOCIAL] %s", s))
			tui.db.conn.Exec(`INSERT INTO odint_findings (scan_id, domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, ?, 'social', 'Social Media Link', ?, 'info', 'Content', CURRENT_TIMESTAMP)`,
				scanID, domain, s)
			totalFindings++
		}
	}
	tui.addOutput(fmt.Sprintf("[+] Content: %d emails, %d phones, %d social links", len(emails), len(phones), len(socials)))
	modulesRun = append(modulesRun, "CONTENT")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Module 6: WHOIS
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [6/7] WHOIS Lookup...")
	tui.writeLog("Module 6: WHOIS lookup")
	registrar, createdDate, expiryDate, nameServersStr, registrantOrg, rawWhois, whoisErr := odintWhoisLookup(ctx, domain)
	if whoisErr != nil {
		tui.addOutput(fmt.Sprintf("[-] WHOIS error: %v", whoisErr))
		tui.writeLog("WHOIS error: %v", whoisErr)
	} else {
		tui.odintStoreWhois(domain, registrar, createdDate, expiryDate, nameServersStr, registrantOrg, rawWhois)
		if registrar != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Registrar: %s", registrar))
		}
		if createdDate != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Created: %s", createdDate))
		}
		if expiryDate != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Expires: %s", expiryDate))
		}
		if nameServersStr != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Name Servers: %s", nameServersStr))
		}
		if registrantOrg != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Registrant: %s", registrantOrg))
		}
	}
	modulesRun = append(modulesRun, "WHOIS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Module 7: Wayback Machine
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [7/7] Wayback Machine...")
	tui.writeLog("Module 7: Wayback Machine")
	snapshots, wbErr := odintWaybackLookup(ctx, client, domain, 50)
	if wbErr != nil {
		tui.addOutput(fmt.Sprintf("[-] Wayback error: %v", wbErr))
		tui.writeLog("Wayback error: %v", wbErr)
	} else {
		tui.odintStoreWayback(domain, snapshots)
		tui.addOutput(fmt.Sprintf("[+] Wayback: %d snapshots found", len(snapshots)))
		// Show a few
		shown := 0
		for _, s := range snapshots {
			if shown >= 10 {
				tui.addOutput(fmt.Sprintf("    ... and %d more", len(snapshots)-10))
				break
			}
			tui.addOutput(fmt.Sprintf("    [WB] [%s] %s (%s)", s.Timestamp, s.URL, s.MimeType))
			shown++
		}
	}
	modulesRun = append(modulesRun, "WAYBACK")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Optional: Shodan if key configured
	cfg := odintLoadConfig()
	if cfg.ShodanKey != "" && ctx.Err() == nil {
		tui.addOutput("[*] [BONUS] Shodan lookup...")
		ports, osName, org, shodanErr := odintQueryShodan(ctx, client, domain, cfg.ShodanKey)
		if shodanErr != nil {
			tui.addOutput(fmt.Sprintf("[-] Shodan: %v", shodanErr))
		} else {
			if len(ports) > 0 {
				portStrs := make([]string, len(ports))
				for i, p := range ports {
					portStrs[i] = strconv.Itoa(p)
				}
				tui.addOutput(fmt.Sprintf("    [SHODAN] Ports: %s", strings.Join(portStrs, ", ")))
			}
			if osName != "" {
				tui.addOutput(fmt.Sprintf("    [SHODAN] OS: %s", osName))
			}
			if org != "" {
				tui.addOutput(fmt.Sprintf("    [SHODAN] Org: %s", org))
			}
		}
		modulesRun = append(modulesRun, "SHODAN")
	}

	// =====================================================================
	// PHASE 2: Enhanced analysis on HTTP body
	// =====================================================================

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [8/35] Enhanced DNS (SRV/DKIM)...")
	tui.odintCurrentModule = "Enhanced DNS"
	{
		enhancedDNS := odintDNSEnhanced(ctx, domain)
		if len(enhancedDNS) > 0 {
			tui.odintStoreDNS(scanID, domain, enhancedDNS)
			for _, r := range enhancedDNS {
				tui.addOutput(fmt.Sprintf("    [DNS+] %s: %s", r.Type, r.Value))
			}
		}
		tui.addOutput(fmt.Sprintf("[+] Enhanced DNS: %d additional records", len(enhancedDNS)))
	}
	modulesRun = append(modulesRun, "DNS+")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [9/35] SSL/TLS deep analysis...")
	tui.odintCurrentModule = "SSL Deep"
	{
		sslDeep := odintSSLDeepAnalysis(ctx, domain)
		if sslDeep != nil {
			if len(sslDeep.CipherSuites) > 0 {
				tui.addOutput(fmt.Sprintf("    [SSL+] Cipher suites: %s", strings.Join(sslDeep.CipherSuites, ", ")))
			}
			if len(sslDeep.WeakCiphers) > 0 {
				tui.addOutput(fmt.Sprintf("[!] Weak ciphers: %s", strings.Join(sslDeep.WeakCiphers, ", ")))
				for _, wc := range sslDeep.WeakCiphers {
					tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
						VALUES (?, 'ssl', 'Weak Cipher', ?, 'medium', 'SSL+', CURRENT_TIMESTAMP)`, domain, wc)
					totalFindings++
				}
			}
			if sslDeep.PFS {
				tui.addOutput("    [SSL+] Perfect Forward Secrecy: YES")
			}
			if sslDeep.OCSPStapling {
				tui.addOutput("    [SSL+] OCSP Stapling: YES")
			}
			if len(sslDeep.ALPNProtocols) > 0 {
				tui.addOutput(fmt.Sprintf("    [SSL+] ALPN: %s", strings.Join(sslDeep.ALPNProtocols, ", ")))
			}
			if sslDeep.HSTSPreload {
				tui.addOutput("    [SSL+] HSTS Preload: YES")
			}
		}
	}
	modulesRun = append(modulesRun, "SSL+")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [10/35] Exposed path detection...")
	tui.odintCurrentModule = "Paths"
	{
		targetURL := "https://" + domain
		paths := odintCheckExposedPaths(ctx, client, targetURL)
		tui.odintStoreExposedPaths(domain, paths)
		for _, p := range paths {
			severity := "info"
			switch p.Type {
			case "credential", "env":
				severity = "critical"
			case "backup", "git", "svn":
				severity = "high"
			case "config", "log", "debug":
				severity = "medium"
			}
			tui.addOutput(fmt.Sprintf("    [PATH] [%d] %s (%s)", p.StatusCode, p.Path, p.Type))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'exposed_path', ?, ?, ?, 'Paths', CURRENT_TIMESTAMP)`,
				domain, fmt.Sprintf("Exposed: %s", p.Path), p.Type, severity)
			totalFindings++
		}
		tui.addOutput(fmt.Sprintf("[+] Exposed paths: %d found", len(paths)))
	}
	modulesRun = append(modulesRun, "PATHS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [11/35] WordPress enumeration...")
	tui.odintCurrentModule = "WordPress"
	{
		targetURL := "https://" + domain
		isWP, wpVer, wpTheme, wpUsers, wpPlugins, wpREST := odintCheckWordPress(ctx, client, targetURL)
		if isWP {
			tui.odintStoreWordPress(domain, isWP, wpVer, wpTheme, wpUsers, wpPlugins, wpREST)
			tui.addOutput(fmt.Sprintf("    [WP] WordPress detected! Version: %s Theme: %s", wpVer, wpTheme))
			if len(wpUsers) > 0 {
				tui.addOutput(fmt.Sprintf("    [WP] Users: %d found", len(wpUsers)))
				for _, u := range wpUsers {
					tui.addOutput(fmt.Sprintf("        [WP-USER] %s (%s) %s", u.Username, u.DisplayName, u.GravatarHash))
				}
			}
			if len(wpPlugins) > 0 {
				tui.addOutput(fmt.Sprintf("    [WP] Plugins: %s", strings.Join(wpPlugins, ", ")))
			}
		} else {
			tui.addOutput("    [WP] Not a WordPress site")
		}
	}
	modulesRun = append(modulesRun, "WP")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// =====================================================================
	// PHASE 3: Body-based analysis
	// =====================================================================

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [12/35] Secrets detection...")
	tui.odintCurrentModule = "Secrets"
	{
		targetURL := "https://" + domain
		secrets := odintDeepSecretScan(targetURL, respBody)
		tui.odintStoreSecrets(domain, secrets)
		for _, s := range secrets {
			tui.addOutput(fmt.Sprintf("    [SECRET] [%s] %s: %s", strings.ToUpper(s.Severity), s.Type, s.Value))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'secret', ?, ?, ?, 'Secrets', CURRENT_TIMESTAMP)`,
				domain, s.Type, s.Value, s.Severity)
			totalFindings++
		}
		tui.addOutput(fmt.Sprintf("[+] Secrets: %d found", len(secrets)))
	}
	modulesRun = append(modulesRun, "SECRETS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [13/35] JWT token analysis...")
	tui.odintCurrentModule = "JWT"
	{
		tokens := odintAnalyzeJWTs("https://"+domain, respBody, httpHeaders)
		tui.odintStoreJWTs(domain, tokens)
		for _, t := range tokens {
			tui.addOutput(fmt.Sprintf("    [JWT] Algorithm: %s Issuer: %s Location: %s", t.Algorithm, t.Issuer, t.Location))
		}
		tui.addOutput(fmt.Sprintf("[+] JWT tokens: %d found", len(tokens)))
	}
	modulesRun = append(modulesRun, "JWT")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [14/35] Meta tags & forms...")
	tui.odintCurrentModule = "Meta/Forms"
	{
		metaTags := odintExtractMetaTags(respBody)
		for k, v := range metaTags {
			if len(metaTags) <= 20 || strings.Contains(k, "og:") || strings.Contains(k, "twitter:") || k == "description" {
				tui.addOutput(fmt.Sprintf("    [META] %s: %s", k, v))
			}
		}
		forms := odintExtractForms(respBody)
		for _, f := range forms {
			tui.addOutput(fmt.Sprintf("    [FORM] %s %s (%s) fields: %d", f.Method, f.Action, f.Type, len(f.Fields)))
		}
		tui.addOutput(fmt.Sprintf("[+] Meta: %d tags, %d forms", len(metaTags), len(forms)))
	}
	modulesRun = append(modulesRun, "META")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [15/35] Schema.org / JSON-LD...")
	tui.odintCurrentModule = "Schema"
	{
		schemaItems := odintExtractSchemaOrg(respBody)
		if len(schemaItems) > 0 {
			for _, item := range schemaItems {
				tui.addOutput(fmt.Sprintf("    [SCHEMA] %s: %s", item.Type, item.ItemType))
				tui.db.conn.Exec(`INSERT INTO odint_structured_data (domain, data_type, item_type, properties, discovered)
					VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`, domain, item.Type, item.ItemType, fmt.Sprintf("%v", item.Properties))
			}
		}
	}
	modulesRun = append(modulesRun, "SCHEMA")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [16/35] Analytics IDs...")
	tui.odintCurrentModule = "Analytics"
	{
		analyticsIDs := odintExtractAnalyticsIDs(respBody)
		for _, id := range analyticsIDs {
			tui.addOutput(fmt.Sprintf("    [ANALYTICS] %s: %s", id.Platform, id.ID))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_analytics_ids (domain, platform, analytics_id, discovered)
				VALUES (?, ?, ?, CURRENT_TIMESTAMP)`, domain, id.Platform, id.ID)
		}
		tui.addOutput(fmt.Sprintf("[+] Analytics: %d IDs found", len(analyticsIDs)))
	}
	modulesRun = append(modulesRun, "ANALYTICS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [17/35] CSP analysis...")
	tui.odintCurrentModule = "CSP"
	{
		cspDirectives := odintAnalyzeCSP(httpHeaders)
		tui.odintStoreCSP(domain, cspDirectives)
		for _, d := range cspDirectives {
			tui.addOutput(fmt.Sprintf("    [CSP] %s: %s", d.Directive, strings.Join(d.Values, " ")))
			for _, v := range d.Values {
				if v == "'unsafe-inline'" || v == "'unsafe-eval'" || v == "*" {
					tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
						VALUES (?, 'csp', 'Unsafe CSP directive', ?, 'medium', 'CSP', CURRENT_TIMESTAMP)`,
						domain, fmt.Sprintf("%s allows %s", d.Directive, v))
					totalFindings++
				}
			}
		}
		if len(cspDirectives) == 0 {
			tui.addOutput("    [CSP] No Content-Security-Policy header found")
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'csp', 'Missing CSP Header', 'No Content-Security-Policy header', 'low', 'CSP', CURRENT_TIMESTAMP)`, domain)
			totalFindings++
		}
	}
	modulesRun = append(modulesRun, "CSP")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [18/35] JavaScript analysis...")
	tui.odintCurrentModule = "JS"
	{
		globals, configs, envLeaks, debugFlags, featureFlags := odintAnalyzeJavaScript(respBody)
		for _, g := range globals {
			tui.addOutput(fmt.Sprintf("    [JS] Global: %s", g))
		}
		for _, c := range configs {
			tui.addOutput(fmt.Sprintf("    [JS] Config: %s", c))
		}
		for _, e := range envLeaks {
			tui.addOutput(fmt.Sprintf("    [JS] Env leak: %s", e))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'js', 'Environment Variable Leak', ?, 'medium', 'JS', CURRENT_TIMESTAMP)`, domain, e)
			totalFindings++
		}
		for _, d := range debugFlags {
			tui.addOutput(fmt.Sprintf("    [JS] Debug: %s", d))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'js', 'Debug Mode Enabled', ?, 'medium', 'JS', CURRENT_TIMESTAMP)`, domain, d)
			totalFindings++
		}
		for _, f := range featureFlags {
			tui.addOutput(fmt.Sprintf("    [JS] Feature flag: %s", f))
		}
	}
	modulesRun = append(modulesRun, "JS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [19/35] Iframe/embed sources...")
	tui.odintCurrentModule = "Iframes"
	{
		iframes := odintExtractIframeSources(respBody)
		tui.odintStoreIframes(domain, iframes)
		for _, iframe := range iframes {
			tui.addOutput(fmt.Sprintf("    [IFRAME] %s (%s)", iframe.URL, iframe.ObjectType))
		}
	}
	modulesRun = append(modulesRun, "IFRAMES")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [20/35] Webhook detection...")
	tui.odintCurrentModule = "Webhooks"
	{
		webhooks := odintDetectWebhookURLs(respBody)
		for _, wh := range webhooks {
			tui.addOutput(fmt.Sprintf("    [WEBHOOK] %s: %s", wh.Platform, wh.URL))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_webhook_urls (domain, platform, url, location, discovered)
				VALUES (?, ?, ?, 'body', CURRENT_TIMESTAMP)`, domain, wh.Platform, wh.URL)
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'webhook', ?, ?, 'high', 'Webhooks', CURRENT_TIMESTAMP)`,
				domain, fmt.Sprintf("%s Webhook Exposed", wh.Platform), wh.URL)
			totalFindings++
		}
	}
	modulesRun = append(modulesRun, "WEBHOOKS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [21/35] Reporting endpoints...")
	tui.odintCurrentModule = "Reporting"
	{
		endpoints := odintExtractReportingEndpoints(httpHeaders)
		tui.odintStoreReportingEndpoints(domain, endpoints)
		for _, ep := range endpoints {
			tui.addOutput(fmt.Sprintf("    [REPORT] %s: %s", ep.Type, ep.URL+ep.Config))
		}
	}
	modulesRun = append(modulesRun, "REPORTING")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [22/35] Hreflang & canonical...")
	tui.odintCurrentModule = "Hreflang"
	{
		hreflangs := odintExtractHreflang(respBody)
		for _, h := range hreflangs {
			tui.addOutput(fmt.Sprintf("    [HREFLANG] %s", h))
		}
		canonical := odintExtractCanonical(respBody)
		if canonical != "" {
			tui.addOutput(fmt.Sprintf("    [CANONICAL] %s", canonical))
		}
		feeds := odintExtractFeeds(respBody)
		for _, f := range feeds {
			tui.addOutput(fmt.Sprintf("    [FEED] %s", f))
		}
	}
	modulesRun = append(modulesRun, "HREFLANG")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [23/35] File discovery...")
	tui.odintCurrentModule = "Files"
	{
		targetURL := "https://" + domain
		files := odintDiscoverFiles(respBody, targetURL)
		tui.odintStoreDiscoveredFiles(domain, files)
		tui.addOutput(fmt.Sprintf("[+] Files: %d discovered", len(files)))
		shown := 0
		for _, f := range files {
			if shown >= 10 {
				tui.addOutput(fmt.Sprintf("    ... and %d more", len(files)-10))
				break
			}
			tui.addOutput(fmt.Sprintf("    [FILE] %s (%s)", f.Filename, f.FileType))
			shown++
		}
	}
	modulesRun = append(modulesRun, "FILES")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [24/35] CORS analysis...")
	tui.odintCurrentModule = "CORS"
	{
		corsFindings := odintAnalyzeCORS(httpHeaders)
		for _, finding := range corsFindings {
			tui.addOutput(fmt.Sprintf("    [CORS] %s", finding))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
				VALUES (?, 'cors', 'CORS Misconfiguration', ?, 'medium', 'CORS', CURRENT_TIMESTAMP)`, domain, finding)
			totalFindings++
		}
	}
	modulesRun = append(modulesRun, "CORS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [25/35] Error page analysis...")
	tui.odintCurrentModule = "ErrorPages"
	{
		targetURL := "https://" + domain
		errorPages := odintCheckErrorPages(ctx, client, targetURL)
		tui.odintStoreErrorPages(domain, errorPages)
		for _, ep := range errorPages {
			if ep.StackTrace {
				tui.addOutput(fmt.Sprintf("    [ERROR] Stack trace leaked on %d page!", ep.StatusCode))
			}
			if len(ep.InternalIPs) > 0 {
				tui.addOutput(fmt.Sprintf("    [ERROR] Internal IPs: %s", strings.Join(ep.InternalIPs, ", ")))
			}
			if ep.Framework != "" {
				tui.addOutput(fmt.Sprintf("    [ERROR] Framework: %s", ep.Framework))
			}
		}
	}
	modulesRun = append(modulesRun, "ERRORS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// =====================================================================
	// PHASE 4: Network-based analysis (slower, sequential)
	// =====================================================================

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [26/35] Subdomain enumeration...")
	tui.odintCurrentModule = "Subdomains"
	{
		subs := odintEnumerateSubdomains(ctx, client, domain, cfg)
		tui.odintStoreSubdomains(domain, subs)
		tui.addOutput(fmt.Sprintf("[+] Subdomains: %d found", len(subs)))
		shown := 0
		for _, s := range subs {
			if shown >= 15 {
				tui.addOutput(fmt.Sprintf("    ... and %d more", len(subs)-15))
				break
			}
			tui.addOutput(fmt.Sprintf("    [SUB] %s (%s) %s", s.Subdomain, s.Source, s.IP))
			shown++
		}
	}
	modulesRun = append(modulesRun, "SUBS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [27/35] GraphQL endpoint detection...")
	tui.odintCurrentModule = "GraphQL"
	{
		targetURL := "https://" + domain
		gqlEndpoints := odintDetectGraphQL(ctx, client, targetURL)
		tui.odintStoreGraphQL(domain, gqlEndpoints)
		for _, ep := range gqlEndpoints {
			intro := "no introspection"
			if ep.Introspection {
				intro = fmt.Sprintf("introspection ON (%d types, %d queries)", len(ep.Types), len(ep.Queries))
			}
			tui.addOutput(fmt.Sprintf("    [GQL] %s - %s", ep.URL, intro))
		}
	}
	modulesRun = append(modulesRun, "GRAPHQL")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [28/35] Virtual host enumeration...")
	tui.odintCurrentModule = "VHosts"
	{
		vhosts := odintEnumerateVirtualHosts(ctx, client, domain)
		tui.odintStoreVirtualHosts(domain, vhosts)
		tui.addOutput(fmt.Sprintf("[+] Virtual hosts: %d found", len(vhosts)))
		for _, vh := range vhosts {
			if !vh.IsDefault {
				tui.addOutput(fmt.Sprintf("    [VHOST] %s (%s)", vh.Domain, vh.Source))
			}
		}
	}
	modulesRun = append(modulesRun, "VHOSTS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [29/35] Email security DNS...")
	tui.odintCurrentModule = "EmailSec"
	{
		emailSec := odintCheckEmailSecurityDNS(ctx, domain)
		tui.odintStoreEmailSecurity(domain, emailSec)
		if emailSec.MTASTSPolicy != "" {
			tui.addOutput(fmt.Sprintf("    [EMAIL] MTA-STS: %s", emailSec.MTASTSPolicy))
		}
		if emailSec.BIMIRecord != "" {
			tui.addOutput(fmt.Sprintf("    [EMAIL] BIMI: %s", emailSec.BIMIRecord))
		}
		if emailSec.SMTPTLSRpt != "" {
			tui.addOutput(fmt.Sprintf("    [EMAIL] SMTP TLS RPT: %s", emailSec.SMTPTLSRpt))
		}
	}
	modulesRun = append(modulesRun, "EMAILSEC")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [30/35] Google dorks...")
	tui.odintCurrentModule = "Dorks"
	{
		dorks := odintGenerateDorks(domain)
		tui.odintStoreDorks(domain, dorks)
		tui.addOutput(fmt.Sprintf("[+] Generated %d Google dork queries", len(dorks)))
	}
	modulesRun = append(modulesRun, "DORKS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [31/35] Social media check...")
	tui.odintCurrentModule = "Social"
	{
		profiles := odintCheckSocialMedia(ctx, client, domain)
		tui.odintStoreSocialProfiles(domain, profiles)
		for _, p := range profiles {
			if p.Exists {
				tui.addOutput(fmt.Sprintf("    [SOCIAL] %s: %s (exists)", p.Platform, p.URL))
			}
		}
	}
	modulesRun = append(modulesRun, "SOCIAL")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [32/35] Cloud storage check...")
	tui.odintCurrentModule = "Cloud"
	{
		exposures := odintCheckCloudStorage(ctx, client, domain)
		tui.odintStoreCloudExposures(domain, exposures)
		for _, e := range exposures {
			if e.Accessible {
				tui.addOutput(fmt.Sprintf("[!] [CLOUD] %s bucket accessible: %s", e.Provider, e.URL))
				tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings (domain, finding_type, title, detail, severity, source_module, discovered)
					VALUES (?, 'cloud', ?, ?, 'high', 'Cloud', CURRENT_TIMESTAMP)`,
					domain, fmt.Sprintf("Accessible %s bucket", e.Provider), e.URL)
				totalFindings++
			}
		}
	}
	modulesRun = append(modulesRun, "CLOUD")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [33/35] Network intelligence...")
	tui.odintCurrentModule = "NetIntel"
	{
		// Resolve domain to IP for network intel
		ips, _ := net.LookupHost(domain)
		if len(ips) > 0 {
			intel := odintLookupNetworkIntel(ctx, client, ips[0])
			tui.odintStoreNetworkIntel(domain, ips[0], intel)
			if intel != nil {
				tui.addOutput(fmt.Sprintf("    [NET] ASN: %s (%s)", intel.ASN, intel.ASNOrg))
				tui.addOutput(fmt.Sprintf("    [NET] Location: %s, %s", intel.City, intel.Country))
				tui.addOutput(fmt.Sprintf("    [NET] ISP: %s", intel.ISP))
			}
		}
	}
	modulesRun = append(modulesRun, "NETINTEL")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// =====================================================================
	// PHASE 4b: Gap-fill modules
	// =====================================================================

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [34/42] Cookie fingerprinting...")
	tui.odintCurrentModule = "Cookies"
	{
		if savedCookies != nil {
			cfs := odintExtractCookieFingerprints(savedCookies)
			tui.odintStoreCookieFingerprints(domain, cfs)
			for _, cf := range cfs {
				tui.addOutput(fmt.Sprintf("    [COOKIE] %s -> %s", cf.Name, cf.TechMatch))
			}
		}
	}
	modulesRun = append(modulesRun, "COOKIES")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [35/42] Management UI detection...")
	tui.odintCurrentModule = "MgmtUI"
	{
		mgmtUIs := odintDetectManagementUIs(ctx, client, baseURL)
		tui.odintStoreManagementUIs(domain, mgmtUIs)
		for _, ui := range mgmtUIs {
			tui.addOutput(fmt.Sprintf("    [MGMT] %s (%d) %s", ui.Type, ui.StatusCode, ui.URL))
			totalFindings++
		}
	}
	modulesRun = append(modulesRun, "MGMTUI")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [36/42] Hash collection...")
	tui.odintCurrentModule = "Hashes"
	{
		if savedBody != "" {
			hashes := odintCollectHashes(savedBody, domain)
			tui.odintStoreHashCorrelations(domain, hashes)
			if len(hashes) > 0 {
				tui.addOutput(fmt.Sprintf("    [HASH] Collected %d hashes", len(hashes)))
			}
		}
	}
	modulesRun = append(modulesRun, "HASHES")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [37/42] AMP / Structured Data / URL Patterns...")
	tui.odintCurrentModule = "PageIntel"
	{
		if savedBody != "" {
			ampInfo := odintDetectAMP(ctx, client, baseURL, savedBody)
			if ampInfo != nil && ampInfo.HasAMP {
				tui.addOutput(fmt.Sprintf("    [AMP] Detected: %s", ampInfo.AMPURL))
			}
			sdItems := odintExtractStructuredDataFull(savedBody)
			tui.odintStoreStructuredData(domain, sdItems)
			if len(sdItems) > 0 {
				tui.addOutput(fmt.Sprintf("    [SD] %d structured data items", len(sdItems)))
			}
			urlPat := odintAnalyzeURLPatterns(savedBody, baseURL)
			if urlPat != nil && urlPat.PaginationMax > 0 {
				tui.addOutput(fmt.Sprintf("    [URL] Pagination max: %d", urlPat.PaginationMax))
			}
		}
	}
	modulesRun = append(modulesRun, "PAGEINTEL")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [38/42] Enhanced robots.txt / Timezone...")
	tui.odintCurrentModule = "Robots"
	{
		robotsIntel := odintEnhancedRobotsTxt(ctx, client, baseURL)
		if robotsIntel != nil && len(robotsIntel.Disallows) > 0 {
			tui.addOutput(fmt.Sprintf("    [ROBOTS] %d disallow rules, %d hidden paths", len(robotsIntel.Disallows), len(robotsIntel.HiddenPaths)))
		}
		if savedHeaders != nil {
			tzInfo := odintExtractTimezoneLocale(savedHeaders, savedBody)
			if tzInfo != nil && tzInfo.Locale != "" {
				tui.addOutput(fmt.Sprintf("    [TZ] Locale: %s", tzInfo.Locale))
			}
		}
	}
	modulesRun = append(modulesRun, "ROBOTS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [39/42] DNS DKIM / SRV records...")
	tui.odintCurrentModule = "DKIM"
	{
		dkimResults := odintLookupDKIM(ctx, domain)
		for _, r := range dkimResults {
			tui.addOutput(fmt.Sprintf("    [DKIM] %s", r))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_dns (domain, record_type, record_value, discovered)
				VALUES (?, 'DKIM', ?, CURRENT_TIMESTAMP)`, domain, r)
		}
		srvResults := odintLookupSRV(ctx, domain)
		for _, r := range srvResults {
			tui.addOutput(fmt.Sprintf("    [SRV] %s", r))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_dns (domain, record_type, record_value, discovered)
				VALUES (?, 'SRV', ?, CURRENT_TIMESTAMP)`, domain, r)
		}
	}
	modulesRun = append(modulesRun, "DKIM_SRV")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [40/42] Historical data (Archive.today / Common Crawl)...")
	tui.odintCurrentModule = "Historical"
	{
		archiveRecs := odintFetchArchiveToday(ctx, client, domain)
		tui.odintStoreHistoricalRecords(domain, archiveRecs)
		if len(archiveRecs) > 0 {
			tui.addOutput(fmt.Sprintf("    [ARCHIVE] %d archive.today snapshots", len(archiveRecs)))
		}
		ccRecs := odintFetchCommonCrawl(ctx, client, domain)
		tui.odintStoreHistoricalRecords(domain, ccRecs)
		if len(ccRecs) > 0 {
			tui.addOutput(fmt.Sprintf("    [CC] %d Common Crawl records", len(ccRecs)))
		}
	}
	modulesRun = append(modulesRun, "HISTORICAL")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [41/42] Credential scanner (521 paths, 4-stage validation)...")
	tui.odintCurrentModule = "CredScan"
	{
		credFindings := odintScanCredentials(ctx, client, baseURL)
		for _, f := range credFindings {
			tui.addOutput(fmt.Sprintf("    [CRED] [%s] %s at %s", strings.ToUpper(f.Severity), f.Type, f.Location))
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_secrets
				(domain, secret_type, value, location, severity, context, discovered) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
				domain, f.Type, f.Value, f.Location, f.Severity, f.Context)
			totalFindings++
		}
		if len(credFindings) > 0 {
			tui.addOutput(fmt.Sprintf("[+] Credential scan: %d exposed files found", len(credFindings)))
		}
	}
	modulesRun = append(modulesRun, "CREDSCAN")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// =====================================================================
	// PHASE 5: External APIs (require keys, slow)
	// =====================================================================

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [42/42] Nmap service scan + External APIs...")
	tui.odintCurrentModule = "Nmap"
	{
		nmapPorts, nmapErr := odintRunNmap(ctx, domain)
		if nmapErr != nil {
			tui.addOutput(fmt.Sprintf("    [NMAP] %v", nmapErr))
		} else {
			tui.odintStoreNmap(domain, nmapPorts)
			for _, p := range nmapPorts {
				tui.addOutput(fmt.Sprintf("    [NMAP] %d/%s %s %s %s", p.Port, p.Protocol, p.Service, p.Product, p.Version))
			}
			tui.addOutput(fmt.Sprintf("[+] Nmap: %d open ports", len(nmapPorts)))
			// CVE enrichment
			if len(nmapPorts) > 0 {
				tui.addOutput("[*] CVE enrichment (may take a while)...")
				nmapPorts = odintEnrichNmapWithCVEs(ctx, nmapPorts, UserAgent)
				tui.odintStoreCVEs(domain, nmapPorts)
				for _, p := range nmapPorts {
					for _, cve := range p.CVEs {
						tui.addOutput(fmt.Sprintf("    [CVE] %s (%.1f %s)", cve.ID, cve.Score, cve.Severity))
						if cve.Score >= 7.0 {
							totalFindings++
						}
					}
				}
			}
		}
	}
	modulesRun = append(modulesRun, "NMAP")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	if ctx.Err() != nil {
		goto finalize
	}
	tui.addOutput("[*] [35/35] External API enrichment...")
	tui.odintCurrentModule = "APIs"
	{
		// GitHub OSINT
		if cfg.HasGithubToken() {
			ghFindings := odintGithubSearch(ctx, client, domain, cfg.GithubToken)
			for _, f := range ghFindings {
				tui.addOutput(fmt.Sprintf("    [GITHUB] %s: %s", f.Name, f.URL))
				tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_github_findings (domain, finding_type, name, url, snippet, discovered)
					VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`, domain, f.Type, f.Name, f.URL, f.Snippet)
			}
		}
		// HIBP (if emails found and key configured)
		if cfg.HasHIBPKey() {
			breachEmails, _ := tui.db.conn.Query(`SELECT detail FROM odint_findings WHERE domain = ? AND finding_type = 'email'`, domain)
			if breachEmails != nil {
				for breachEmails.Next() {
					var email string
					breachEmails.Scan(&email)
					breaches := odintHIBPLookup(ctx, client, email, cfg.HIBPKey)
					for _, b := range breaches {
						tui.addOutput(fmt.Sprintf("    [HIBP] %s breached in %s", email, b.BreachName))
						tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_hibp_breaches (domain, email, breach_name, breach_date, data_classes, pwn_count, discovered)
							VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
							domain, email, b.BreachName, b.BreachDate, strings.Join(b.DataClasses, ","), b.PwnCount)
					}
					time.Sleep(1500 * time.Millisecond) // HIBP rate limit
				}
				breachEmails.Close()
			}
		}
		// Additional APIs (all skip gracefully if no key)
		if data := odintCensysSearch(ctx, client, domain, cfg); data != nil {
			tui.odintStoreAPIv2Result(domain, "Censys", data)
			tui.addOutput("    [API] Censys data collected")
		}
		if data := odintVirusTotalLookup(ctx, client, domain, cfg); data != nil {
			tui.odintStoreAPIv2Result(domain, "VirusTotal", data)
			tui.addOutput("    [API] VirusTotal data collected")
		}
		if data := odintSecurityTrailsLookup(ctx, client, domain, cfg); data != nil {
			tui.odintStoreAPIv2Result(domain, "SecurityTrails", data)
			tui.addOutput("    [API] SecurityTrails data collected")
		}
		if data := odintHunterIOSearch(ctx, client, domain, cfg); data != nil {
			tui.odintStoreAPIv2Result(domain, "Hunter.io", data)
			tui.addOutput("    [API] Hunter.io data collected")
		}
		// BGPView (free, no key)
		ips, _ := net.LookupHost(domain)
		if len(ips) > 0 {
			if data := odintBGPViewLookup(ctx, client, ips[0]); data != nil {
				tui.odintStoreAPIv2Result(domain, "BGPView", data)
				tui.addOutput("    [API] BGPView data collected")
			}
		}
	}
	modulesRun = append(modulesRun, "APIS")
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

finalize:
	// Update scan record
	tui.odintModulesRun = strings.Join(modulesRun, ",")
	tui.db.conn.Exec(`UPDATE odint_scans SET ended = CURRENT_TIMESTAMP, status = 'complete', modules_run = ?, findings_count = ? WHERE id = ?`,
		tui.odintModulesRun, totalFindings, scanID)

	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("[+] === Recon complete: %s ===", domain))
	tui.addOutput(fmt.Sprintf("[+] Modules: %d run (%s)", len(modulesRun), tui.odintModulesRun))
	tui.addOutput(fmt.Sprintf("[+] Findings: %d", totalFindings))
	tui.writeLog("Recon complete: %d findings, modules: %s", totalFindings, tui.odintModulesRun)
}

// ===========================================================================
// SCANNER ORCHESTRATION — Bulk Recon
// ===========================================================================

func (tui *TUI) runOdintBulkRecon(ctx context.Context, domains []string) {
	tui.startLog("bulk-recon")
	defer tui.closeLog()

	total := len(domains)
	atomic.StoreInt64(&tui.odintTotal, int64(total))
	atomic.StoreInt64(&tui.odintProgress, 0)

	tui.addOutput(fmt.Sprintf("[*] Bulk recon: %d domains", total))
	tui.writeLog("Bulk recon: %d domains", total)

	for i, domain := range domains {
		if ctx.Err() != nil {
			tui.addOutput("[!] Bulk recon cancelled")
			return
		}
		tui.addOutput(fmt.Sprintf("[*] --- Domain %d/%d: %s ---", i+1, total, domain))
		tui.runOdintFullRecon(ctx, domain)
		atomic.StoreInt64(&tui.odintProgress, int64(i+1))
		tui.Render()
	}

	tui.addOutput(fmt.Sprintf("[+] Bulk recon complete: %d domains processed", total))
}

// ===========================================================================
// SCANNER ORCHESTRATION — Single Module Runs
// ===========================================================================

func (tui *TUI) runOdintDNSOnly(ctx context.Context, domain string) {
	tui.startLog("dns-only")
	defer tui.closeLog()

	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintTotal, 1)
	atomic.StoreInt64(&tui.odintProgress, 0)

	tui.addOutput(fmt.Sprintf("[*] DNS Enumeration: %s", domain))
	tui.writeLog("DNS-only scan on %s", domain)

	res, _ := tui.db.conn.Exec(`INSERT INTO odint_scans (domain, scan_type, started, status) VALUES (?, 'dns', CURRENT_TIMESTAMP, 'running')`, domain)
	scanID, _ := res.LastInsertId()

	records, err := odintDNSEnumerate(ctx, domain)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] DNS error: %v", err))
	} else {
		tui.odintStoreDNS(scanID, domain, records)
		for _, r := range records {
			tui.addOutput(fmt.Sprintf("    [DNS] %s: %s", r.Type, r.Value))
		}
		tui.addOutput(fmt.Sprintf("[+] DNS: %d records found", len(records)))
	}

	tui.db.conn.Exec(`UPDATE odint_scans SET ended = CURRENT_TIMESTAMP, status = 'complete', modules_run = 'DNS', findings_count = ? WHERE id = ?`,
		len(records), scanID)
	atomic.StoreInt64(&tui.odintProgress, 1)
}

func (tui *TUI) runOdintSSLOnly(ctx context.Context, domain string) {
	tui.startLog("ssl-only")
	defer tui.closeLog()

	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintTotal, 1)
	atomic.StoreInt64(&tui.odintProgress, 0)

	tui.addOutput(fmt.Sprintf("[*] SSL/TLS Analysis: %s", domain))
	tui.writeLog("SSL-only scan on %s", domain)

	res, _ := tui.db.conn.Exec(`INSERT INTO odint_scans (domain, scan_type, started, status) VALUES (?, 'ssl', CURRENT_TIMESTAMP, 'running')`, domain)
	scanID, _ := res.LastInsertId()

	issuer, subject, serial, notBefore, notAfter, sigAlg, keySize, sanNames, isExpired, tlsVersions, err := odintSSLAnalyze(ctx, domain, tui.odintTimeout)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] SSL error: %v", err))
		tui.db.conn.Exec(`UPDATE odint_scans SET ended = CURRENT_TIMESTAMP, status = 'error' WHERE id = ?`, scanID)
	} else {
		tui.odintStoreSSL(scanID, domain, issuer, subject, serial, notBefore, notAfter, sigAlg, keySize, sanNames, isExpired)
		tui.addOutput(fmt.Sprintf("    [SSL] Issuer: %s", issuer))
		tui.addOutput(fmt.Sprintf("    [SSL] Subject: %s", subject))
		tui.addOutput(fmt.Sprintf("    [SSL] Serial: %s", serial))
		tui.addOutput(fmt.Sprintf("    [SSL] Valid: %s to %s", notBefore.Format("2006-01-02"), notAfter.Format("2006-01-02")))
		tui.addOutput(fmt.Sprintf("    [SSL] Algorithm: %s | Key: %d bits", sigAlg, keySize))
		if isExpired {
			tui.addOutput("[!] CERTIFICATE IS EXPIRED")
		}
		if len(sanNames) > 0 {
			tui.addOutput(fmt.Sprintf("    [SSL] SANs (%d): %s", len(sanNames), strings.Join(sanNames, ", ")))
		}
		if len(tlsVersions) > 0 {
			tui.addOutput(fmt.Sprintf("    [SSL] TLS versions: %s", strings.Join(tlsVersions, ", ")))
		}
		tui.db.conn.Exec(`UPDATE odint_scans SET ended = CURRENT_TIMESTAMP, status = 'complete', modules_run = 'SSL' WHERE id = ?`, scanID)
	}
	atomic.StoreInt64(&tui.odintProgress, 1)
}

func (tui *TUI) runOdintTechOnly(ctx context.Context, domain string) {
	tui.startLog("tech-only")
	defer tui.closeLog()

	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintTotal, 1)
	atomic.StoreInt64(&tui.odintProgress, 0)

	tui.addOutput(fmt.Sprintf("[*] Technology Fingerprinting: %s", domain))
	tui.writeLog("Tech-only scan on %s", domain)

	res, _ := tui.db.conn.Exec(`INSERT INTO odint_scans (domain, scan_type, started, status) VALUES (?, 'tech', CURRENT_TIMESTAMP, 'running')`, domain)
	scanID, _ := res.LastInsertId()

	client := tui.odintCreateHTTPClient()
	targetURL := "https://" + domain
	req, reqErr := http.NewRequestWithContext(ctx, "GET", targetURL, nil)
	if reqErr != nil {
		tui.addOutput(fmt.Sprintf("[-] Request error: %v", reqErr))
		return
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, respErr := client.Do(req)
	if respErr != nil {
		// Fallback to HTTP
		targetURL = "http://" + domain
		req, _ = http.NewRequestWithContext(ctx, "GET", targetURL, nil)
		req.Header.Set("User-Agent", UserAgent)
		resp, respErr = client.Do(req)
	}
	if respErr != nil {
		tui.addOutput(fmt.Sprintf("[-] HTTP error: %v", respErr))
		tui.db.conn.Exec(`UPDATE odint_scans SET ended = CURRENT_TIMESTAMP, status = 'error' WHERE id = ?`, scanID)
		return
	}
	defer resp.Body.Close()

	bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024))
	body := string(bodyBytes)

	headers := make(map[string]string)
	for k, vals := range resp.Header {
		headers[k] = strings.Join(vals, ", ")
	}
	var cookies []string
	for _, c := range resp.Cookies() {
		cookies = append(cookies, c.Name+"="+c.Value)
	}

	signatures := odintLoadSignatures()
	techs := odintDetectTechnologies(headers, cookies, body, signatures)
	tui.odintStoreTech(scanID, domain, techs)

	if len(techs) > 0 {
		categories := make(map[string][]string)
		for _, t := range techs {
			categories[t.Category] = append(categories[t.Category], t.Name)
		}
		catKeys := make([]string, 0, len(categories))
		for k := range categories {
			catKeys = append(catKeys, k)
		}
		sort.Strings(catKeys)
		for _, cat := range catKeys {
			tui.addOutput(fmt.Sprintf("    [TECH] %s: %s", cat, strings.Join(categories[cat], ", ")))
		}
		tui.addOutput(fmt.Sprintf("[+] Detected %d technologies across %d categories", len(techs), len(categories)))
	} else {
		tui.addOutput("[-] No technologies detected")
	}

	tui.db.conn.Exec(`UPDATE odint_scans SET ended = CURRENT_TIMESTAMP, status = 'complete', modules_run = 'TECH', findings_count = ? WHERE id = ?`,
		len(techs), scanID)
	atomic.StoreInt64(&tui.odintProgress, 1)
}

func (tui *TUI) runOdintLookupOnly(ctx context.Context, domain string) {
	tui.startLog("lookup-only")
	defer tui.closeLog()

	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintTotal, 2)
	atomic.StoreInt64(&tui.odintProgress, 0)

	tui.addOutput(fmt.Sprintf("[*] WHOIS + Wayback Lookup: %s", domain))
	tui.writeLog("Lookup-only scan on %s", domain)

	// WHOIS
	tui.addOutput("[*] [1/2] WHOIS...")
	registrar, createdDate, expiryDate, nameServersStr, registrantOrg, rawWhois, whoisErr := odintWhoisLookup(ctx, domain)
	if whoisErr != nil {
		tui.addOutput(fmt.Sprintf("[-] WHOIS error: %v", whoisErr))
	} else {
		tui.odintStoreWhois(domain, registrar, createdDate, expiryDate, nameServersStr, registrantOrg, rawWhois)
		if registrar != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Registrar: %s", registrar))
		}
		if createdDate != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Created: %s", createdDate))
		}
		if expiryDate != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Expires: %s", expiryDate))
		}
		if nameServersStr != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Name Servers: %s", nameServersStr))
		}
		if registrantOrg != "" {
			tui.addOutput(fmt.Sprintf("    [WHOIS] Registrant: %s", registrantOrg))
		}
	}
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	// Wayback
	if ctx.Err() != nil {
		return
	}
	tui.addOutput("[*] [2/2] Wayback Machine...")
	client := tui.odintCreateHTTPClient()
	snapshots, wbErr := odintWaybackLookup(ctx, client, domain, 100)
	if wbErr != nil {
		tui.addOutput(fmt.Sprintf("[-] Wayback error: %v", wbErr))
	} else {
		tui.odintStoreWayback(domain, snapshots)
		tui.addOutput(fmt.Sprintf("[+] Wayback: %d snapshots", len(snapshots)))
		shown := 0
		for _, s := range snapshots {
			if shown >= 15 {
				tui.addOutput(fmt.Sprintf("    ... and %d more", len(snapshots)-15))
				break
			}
			tui.addOutput(fmt.Sprintf("    [WB] [%s] %s", s.Timestamp, s.URL))
			shown++
		}
	}
	atomic.AddInt64(&tui.odintProgress, 1)
}

// ===========================================================================
// NEW STANDALONE RUNNERS — Nmap, Subs, Paths, Download, Report
// ===========================================================================

func (tui *TUI) runOdintNmapOnly(ctx context.Context, domain string) {
	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintProgress, 0)
	atomic.StoreInt64(&tui.odintTotal, 2)
	tui.addOutput(fmt.Sprintf("[*] === Nmap Scan + CVE: %s ===", domain))

	tui.addOutput("[*] [1/2] Running Nmap service scan...")
	ports, nmapErr := odintRunNmap(ctx, domain)
	if nmapErr != nil {
		tui.addOutput(fmt.Sprintf("[-] Nmap: %v", nmapErr))
	} else {
		tui.odintStoreNmap(domain, ports)
		for _, p := range ports {
			tui.addOutput(fmt.Sprintf("    [NMAP] %d/%s %s %s %s", p.Port, p.Protocol, p.Service, p.Product, p.Version))
		}
		tui.addOutput(fmt.Sprintf("[+] Nmap: %d open ports", len(ports)))
	}
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.Render()

	tui.addOutput("[*] [2/2] CVE enrichment...")
	if ports != nil && len(ports) > 0 {
		ports = odintEnrichNmapWithCVEs(ctx, ports, UserAgent)
		tui.odintStoreCVEs(domain, ports)
		for _, p := range ports {
			for _, cve := range p.CVEs {
				tui.addOutput(fmt.Sprintf("    [CVE] %s (%.1f %s) %s", cve.ID, cve.Score, cve.Severity, cve.Description))
			}
		}
	}
	atomic.AddInt64(&tui.odintProgress, 1)
	tui.addOutput(fmt.Sprintf("[+] === Nmap scan complete: %s ===", domain))
}

func (tui *TUI) runOdintSubsOnly(ctx context.Context, domain string) {
	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintProgress, 0)
	atomic.StoreInt64(&tui.odintTotal, 1)
	tui.addOutput(fmt.Sprintf("[*] === Subdomain Enumeration: %s ===", domain))

	client := tui.odintCreateHTTPClient()
	cfg := odintLoadConfig()
	subs := odintEnumerateSubdomains(ctx, client, domain, cfg)
	tui.odintStoreSubdomains(domain, subs)

	for _, s := range subs {
		tui.addOutput(fmt.Sprintf("    [SUB] %s (%s) IP: %s", s.Subdomain, s.Source, s.IP))
	}
	tui.addOutput(fmt.Sprintf("[+] Subdomains: %d found", len(subs)))
	atomic.AddInt64(&tui.odintProgress, 1)
}

func (tui *TUI) runOdintPathsOnly(ctx context.Context, domain string) {
	tui.odintCurrentDomain = domain
	atomic.StoreInt64(&tui.odintProgress, 0)
	atomic.StoreInt64(&tui.odintTotal, 1)
	tui.addOutput(fmt.Sprintf("[*] === Exposed Path Detection: %s ===", domain))

	client := tui.odintCreateHTTPClient()
	baseURL := "https://" + domain
	paths := odintCheckExposedPaths(ctx, client, baseURL)
	tui.odintStoreExposedPaths(domain, paths)

	for _, p := range paths {
		severity := "info"
		switch p.Type {
		case "credential", "env":
			severity = "critical"
		case "backup", "git", "svn":
			severity = "high"
		case "config", "log", "debug":
			severity = "medium"
		}
		tui.addOutput(fmt.Sprintf("    [PATH] [%d] %s (%s) [%s]", p.StatusCode, p.Path, p.Type, severity))
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings
			(domain, finding_type, title, detail, severity, source_module, discovered)
			VALUES (?, 'exposed_path', ?, ?, ?, 'Paths', CURRENT_TIMESTAMP)`,
			domain, fmt.Sprintf("Exposed: %s", p.Path), p.Type, severity)
	}
	tui.addOutput(fmt.Sprintf("[+] Exposed paths: %d found", len(paths)))
	atomic.AddInt64(&tui.odintProgress, 1)
}

func (tui *TUI) runOdintDownload(ctx context.Context, domain string) {
	tui.addOutput(fmt.Sprintf("[*] === Download Files for: %s ===", domain))

	// Query discovered files
	rows, err := tui.db.conn.Query(`SELECT url, filename, file_type, source FROM odint_discovered_files WHERE domain = ? AND downloaded = 0`, domain)
	if err != nil {
		tui.addOutput(fmt.Sprintf("[-] DB error: %v", err))
		return
	}
	var files []OdintDiscoveredFile
	for rows.Next() {
		var f OdintDiscoveredFile
		rows.Scan(&f.URL, &f.Filename, &f.FileType, &f.Source)
		files = append(files, f)
	}
	rows.Close()

	if len(files) == 0 {
		tui.addOutput("[-] No undiscovered files to download. Run full recon first.")
		return
	}

	tui.addOutput(fmt.Sprintf("[*] Found %d files to download", len(files)))
	client := tui.odintCreateHTTPClient()
	success, failed := tui.odintDownloadFiles(ctx, client, domain, files)
	tui.addOutput(fmt.Sprintf("[+] Download complete: %d success, %d failed", success, failed))
}

func (tui *TUI) runOdintReport(domain string) {
	tui.addOutput(fmt.Sprintf("[*] === Generating Reports for: %s ===", domain))
	htmlPath := tui.odintGenerateHTMLReport(domain)
	tui.addOutput(fmt.Sprintf("[+] HTML report: %s", htmlPath))
	jsonPath := tui.odintGenerateJSONReport(domain)
	tui.addOutput(fmt.Sprintf("[+] JSON report: %s", jsonPath))
}

// ===========================================================================
// COMMAND HANDLER — startODINTCommand
// ===========================================================================

func (tui *TUI) startODINTCommand(cmdKey string) {
	tui.currentCmd = cmdKey
	tui.collectedInputs = make(map[string]string)

	switch cmdKey {
	case "1": // Run — file picker or single domain
		files, _ := filepath.Glob(filepath.Join(tui.listsDir, "*.txt"))
		csvFiles, _ := filepath.Glob(filepath.Join(tui.listsDir, "*.csv"))
		listFiles, _ := filepath.Glob(filepath.Join(tui.listsDir, "*.list"))
		files = append(files, csvFiles...)
		files = append(files, listFiles...)
		// Also check targets/ folder
		tFiles, _ := filepath.Glob(filepath.Join(tui.targetsDir, "*.txt"))
		tCsv, _ := filepath.Glob(filepath.Join(tui.targetsDir, "*.csv"))
		tList, _ := filepath.Glob(filepath.Join(tui.targetsDir, "*.list"))
		files = append(files, tFiles...)
		files = append(files, tCsv...)
		files = append(files, tList...)
		if len(files) > 0 {
			tui.pickerFiles = nil
			for _, f := range files {
				tui.pickerFiles = append(tui.pickerFiles, f)
			}
			tui.pickerSelected = 0
			tui.commandState = StateFilePicker
			tui.addOutput(fmt.Sprintf("[*] RUN - Found %d target files. Select one or type a single domain.", len(files)))
			return
		}
		// No files found — prompt for single domain
		tui.inputFields = []string{"domain"}
		tui.addOutput("[*] RUN - No target files in lists/ folder")
		tui.addOutput("[*] Drop .txt/.csv/.list files into lists/ or enter a single domain")
		tui.inputPrompt = "Target domain"
	case "2": // Config
		tui.odintConfigWizard()
		return
	case "3": // Stats
		tui.showOdintStats()
		return
	case "4": // Report
		tui.inputFields = []string{"domain"}
		tui.addOutput("[*] REPORT - Generate HTML/JSON report for a domain")
		tui.inputPrompt = "Domain"
	case "5": // Export
		tui.exportOdintResults()
		return
	case "C", "c": // Clear
		tui.clearOutput()
		tui.addOutput("[*] Output cleared")
		return
	case "Q", "q":
		tui.showModuleSelect = true
		return
	default:
		return
	}

	tui.currentField = 0
	tui.inputBuffer = ""
	tui.inputCursor = 0
	tui.commandState = StateInput
}

// ===========================================================================
// COMMAND EXECUTION — executeODINTCommand
// ===========================================================================

func (tui *TUI) executeODINTCommand() {
	tui.commandState = StateRunning
	tui.Render()

	ctx, cancel := context.WithCancel(context.Background())
	tui.cancelScan = cancel

	go func() {
		defer func() { tui.cancelScan = nil }()

		domain := strings.TrimSpace(tui.collectedInputs["domain"])
		// Strip protocol if present
		domain = strings.TrimPrefix(domain, "https://")
		domain = strings.TrimPrefix(domain, "http://")
		domain = strings.TrimSuffix(domain, "/")
		domain = strings.TrimPrefix(domain, "www.")

		switch tui.currentCmd {
		case "1": // Run full suite
			tui.runOdintFullRecon(ctx, domain)
		case "4": // Report
			tui.runOdintReport(domain)
		}

		if ctx.Err() == nil {
			tui.commandState = StateComplete
		}
		tui.Render()
	}()
}

// ===========================================================================
// INPUT HANDLER — odintHandleInput
// ===========================================================================

func (tui *TUI) odintHandleInput(input string) {
	domain := strings.TrimSpace(input)
	domain = strings.TrimPrefix(domain, "https://")
	domain = strings.TrimPrefix(domain, "http://")
	domain = strings.TrimSuffix(domain, "/")
	domain = strings.TrimPrefix(domain, "www.")

	if domain == "" {
		tui.addOutput("[-] Invalid domain")
		tui.commandState = StateMenu
		return
	}

	tui.collectedInputs["domain"] = domain
	tui.executeODINTCommand()
}

// ===========================================================================
// CONFIG WIZARD
// ===========================================================================

func (tui *TUI) odintConfigWizard() {
	cfg := odintLoadConfig()

	maskKey := func(key string) string {
		if key == "" {
			return "(not set)"
		}
		if len(key) <= 12 {
			return key[:4] + "..."
		}
		return key[:8] + "..." + key[len(key)-4:]
	}

	tui.addOutput("")
	tui.addOutput("[*] === ODINT Configuration (20+ API Keys) ===")
	tui.addOutput("")

	tui.addOutput("[*] Core APIs:")
	tui.addOutput(fmt.Sprintf("    Shodan API Key:       %s", maskKey(cfg.ShodanKey)))
	tui.addOutput(fmt.Sprintf("    HIBP API Key:         %s", maskKey(cfg.HIBPKey)))
	tui.addOutput(fmt.Sprintf("    GitHub Token:         %s", maskKey(cfg.GithubToken)))
	tui.addOutput("")

	tui.addOutput("[*] Threat Intelligence:")
	tui.addOutput(fmt.Sprintf("    Censys API ID:        %s", maskKey(cfg.CensysAPIID)))
	tui.addOutput(fmt.Sprintf("    Censys API Secret:    %s", maskKey(cfg.CensysAPISecret)))
	tui.addOutput(fmt.Sprintf("    VirusTotal Key:       %s", maskKey(cfg.VirusTotalKey)))
	tui.addOutput(fmt.Sprintf("    SecurityTrails Key:   %s", maskKey(cfg.SecurityTrailsKey)))
	tui.addOutput(fmt.Sprintf("    GreyNoise Key:        %s", maskKey(cfg.GreyNoiseKey)))
	tui.addOutput(fmt.Sprintf("    BinaryEdge Key:       %s", maskKey(cfg.BinaryEdgeKey)))
	tui.addOutput(fmt.Sprintf("    ZoomEye Key:          %s", maskKey(cfg.ZoomEyeKey)))
	tui.addOutput("")

	tui.addOutput("[*] Email & Identity:")
	tui.addOutput(fmt.Sprintf("    Hunter.io Key:        %s", maskKey(cfg.HunterIOKey)))
	tui.addOutput(fmt.Sprintf("    DeHashed Email:       %s", maskKey(cfg.DeHashedEmail)))
	tui.addOutput(fmt.Sprintf("    DeHashed Key:         %s", maskKey(cfg.DeHashedKey)))
	tui.addOutput(fmt.Sprintf("    LeakCheck Key:        %s", maskKey(cfg.LeakCheckKey)))
	tui.addOutput(fmt.Sprintf("    Snusbase Key:         %s", maskKey(cfg.SnusbaseKey)))
	tui.addOutput(fmt.Sprintf("    IntelX Key:           %s", maskKey(cfg.IntelXKey)))
	tui.addOutput("")

	tui.addOutput("[*] Network & Search:")
	tui.addOutput(fmt.Sprintf("    IPInfo Token:         %s", maskKey(cfg.IPInfoToken)))
	tui.addOutput(fmt.Sprintf("    FOFA Email:           %s", maskKey(cfg.FOFAEmail)))
	tui.addOutput(fmt.Sprintf("    FOFA Key:             %s", maskKey(cfg.FOFAKey)))
	tui.addOutput("")

	tui.addOutput("[*] Settings:")
	tui.addOutput(fmt.Sprintf("    Max Download Size:    %d MB", cfg.MaxDownloadSizeMB))
	ua := cfg.UserAgent
	if ua == "" {
		ua = "(default)"
	}
	tui.addOutput(fmt.Sprintf("    User Agent:           %s", ua))
	tui.addOutput("")

	tui.addOutput("[*] Config file: " + odintConfigPath())
	tui.addOutput("[*] Enter API keys below (leave blank to skip)")
	tui.addOutput("")

	// Switch to multi-field input — starts with shodan, the handler chains through the rest
	tui.currentCmd = "config"
	tui.inputFields = []string{"shodan_key"}
	tui.inputPrompt = "Shodan API Key (blank=skip)"
	tui.currentField = 0
	tui.inputBuffer = ""
	tui.inputCursor = 0
	tui.commandState = StateInput
}

// ===========================================================================
// STATS
// ===========================================================================

func (tui *TUI) showOdintStats() {
	tui.addOutput("")
	tui.addOutput("[*] === ODINT Database Statistics ===")

	countTable := func(table string) int {
		var n int
		tui.db.conn.QueryRow("SELECT COUNT(*) FROM " + table).Scan(&n)
		return n
	}

	totalScans := countTable("odint_scans")
	var completeScans int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_scans WHERE status = 'complete'").Scan(&completeScans)

	tui.addOutput(fmt.Sprintf("    Scans: %d total (%d complete)", totalScans, completeScans))
	tui.addOutput("")

	// Core tables
	tui.addOutput("[*] Core Data:")
	tui.addOutput(fmt.Sprintf("    DNS Records:      %d", countTable("odint_dns")))
	tui.addOutput(fmt.Sprintf("    SSL Certs:        %d", countTable("odint_ssl")))
	tui.addOutput(fmt.Sprintf("    Technologies:     %d", countTable("odint_technologies")))
	tui.addOutput(fmt.Sprintf("    Findings:         %d", countTable("odint_findings")))
	tui.addOutput(fmt.Sprintf("    WHOIS Records:    %d", countTable("odint_whois")))
	tui.addOutput(fmt.Sprintf("    Wayback Snaps:    %d", countTable("odint_wayback")))

	// New recon modules
	tui.addOutput("")
	tui.addOutput("[*] Recon Modules:")
	tui.addOutput(fmt.Sprintf("    Nmap Results:     %d", countTable("odint_nmap_results")))
	tui.addOutput(fmt.Sprintf("    CVE Findings:     %d", countTable("odint_cve_findings")))
	tui.addOutput(fmt.Sprintf("    WordPress Sites:  %d", countTable("odint_wordpress_sites")))
	tui.addOutput(fmt.Sprintf("    WP Users:         %d", countTable("odint_wordpress_users")))
	tui.addOutput(fmt.Sprintf("    Subdomains:       %d", countTable("odint_subdomains")))
	tui.addOutput(fmt.Sprintf("    Exposed Paths:    %d", countTable("odint_exposed_paths")))
	tui.addOutput(fmt.Sprintf("    Virtual Hosts:    %d", countTable("odint_virtual_hosts")))

	// Analysis modules
	tui.addOutput("")
	tui.addOutput("[*] Analysis:")
	tui.addOutput(fmt.Sprintf("    JWT Tokens:       %d", countTable("odint_jwt_tokens")))
	tui.addOutput(fmt.Sprintf("    GraphQL Endpts:   %d", countTable("odint_graphql_endpoints")))
	tui.addOutput(fmt.Sprintf("    Secrets Found:    %d", countTable("odint_secrets")))
	tui.addOutput(fmt.Sprintf("    CSP Directives:   %d", countTable("odint_csp_directives")))
	tui.addOutput(fmt.Sprintf("    Error Pages:      %d", countTable("odint_error_pages")))
	tui.addOutput(fmt.Sprintf("    Cookie Prints:    %d", countTable("odint_cookie_fingerprints")))
	tui.addOutput(fmt.Sprintf("    Analytics IDs:    %d", countTable("odint_analytics_ids")))
	tui.addOutput(fmt.Sprintf("    Iframes:          %d", countTable("odint_iframe_sources")))
	tui.addOutput(fmt.Sprintf("    Structured Data:  %d", countTable("odint_structured_data")))
	tui.addOutput(fmt.Sprintf("    Webhooks:         %d", countTable("odint_webhook_urls")))
	tui.addOutput(fmt.Sprintf("    Report Endpoints: %d", countTable("odint_reporting_endpoints")))
	tui.addOutput(fmt.Sprintf("    Email Security:   %d", countTable("odint_email_security")))

	// External API / OSINT
	tui.addOutput("")
	tui.addOutput("[*] External Intelligence:")
	tui.addOutput(fmt.Sprintf("    HIBP Breaches:    %d", countTable("odint_hibp_breaches")))
	tui.addOutput(fmt.Sprintf("    GitHub Findings:  %d", countTable("odint_github_findings")))
	tui.addOutput(fmt.Sprintf("    Social Profiles:  %d", countTable("odint_social_profiles")))
	tui.addOutput(fmt.Sprintf("    Cloud Exposures:  %d", countTable("odint_cloud_exposures")))
	tui.addOutput(fmt.Sprintf("    Google Dorks:     %d", countTable("odint_google_dorks")))
	tui.addOutput(fmt.Sprintf("    Network Intel:    %d", countTable("odint_network_intel")))
	tui.addOutput(fmt.Sprintf("    Historical:       %d", countTable("odint_historical_records")))
	tui.addOutput(fmt.Sprintf("    Hash Correlations:%d", countTable("odint_hash_correlations")))
	tui.addOutput(fmt.Sprintf("    Discovered Files: %d", countTable("odint_discovered_files")))
	tui.addOutput(fmt.Sprintf("    Management UIs:   %d", countTable("odint_management_uis")))

	// Top technologies
	tui.addOutput("")
	tui.addOutput("[*] Top Technologies Detected:")
	rows, err := tui.db.conn.Query(`
		SELECT tech_name, tech_category, COUNT(*) as cnt
		FROM odint_technologies
		GROUP BY tech_name
		ORDER BY cnt DESC
		LIMIT 15`)
	if err == nil {
		defer rows.Close()
		for rows.Next() {
			var name, category string
			var cnt int
			rows.Scan(&name, &category, &cnt)
			tui.addOutput(fmt.Sprintf("    %s (%s) - found on %d domains", name, category, cnt))
		}
	}

	// Recent scans
	tui.addOutput("")
	tui.addOutput("[*] Recent Scans:")
	rows2, err2 := tui.db.conn.Query(`
		SELECT domain, scan_type, started, status, modules_run, findings_count
		FROM odint_scans
		ORDER BY started DESC
		LIMIT 10`)
	if err2 == nil {
		defer rows2.Close()
		for rows2.Next() {
			var domain, scanType, started, status, modulesRun string
			var findingsCount int
			rows2.Scan(&domain, &scanType, &started, &status, &modulesRun, &findingsCount)
			tui.addOutput(fmt.Sprintf("    [%s] %s (%s) - %s - %d findings", status, domain, scanType, started, findingsCount))
		}
	}

	// Findings by severity
	tui.addOutput("")
	tui.addOutput("[*] Findings by Severity:")
	rows3, err3 := tui.db.conn.Query(`
		SELECT severity, COUNT(*) as cnt
		FROM odint_findings
		GROUP BY severity
		ORDER BY CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 ELSE 5 END`)
	if err3 == nil {
		defer rows3.Close()
		for rows3.Next() {
			var severity string
			var cnt int
			rows3.Scan(&severity, &cnt)
			tui.addOutput(fmt.Sprintf("    %s: %d", strings.ToUpper(severity), cnt))
		}
	}

	tui.addOutput("")
}

// ===========================================================================
// EXPORT — CSV
// ===========================================================================

func (tui *TUI) exportOdintResults() {
	timestamp := time.Now().Format("2006-01-02_150405")

	csvEsc := func(s string) string {
		return strings.ReplaceAll(strings.ReplaceAll(s, ",", ";"), "\n", " ")
	}

	// Generic CSV exporter: returns count of rows written
	exportCSV := func(filename, header, query string, scanRow func(rows interface{ Scan(...interface{}) error }) string) int {
		fpath := filepath.Join(tui.logsDir, fmt.Sprintf("odint_%s_%s.csv", filename, timestamp))
		f, err := os.Create(fpath)
		if err != nil {
			return 0
		}
		defer f.Close()
		f.WriteString(header + "\n")
		rows, err := tui.db.conn.Query(query)
		if err != nil {
			return 0
		}
		defer rows.Close()
		count := 0
		for rows.Next() {
			line := scanRow(rows)
			if line != "" {
				f.WriteString(line + "\n")
				count++
			}
		}
		tui.addOutput(fmt.Sprintf("[+] %s: %d records -> %s", filename, count, filepath.Base(fpath)))
		return count
	}

	tui.addOutput("")
	tui.addOutput("[*] === ODINT Export Starting ===")
	tui.addOutput("")
	total := 0

	// Findings
	total += exportCSV("findings", "domain,finding_type,title,detail,severity,source_module,discovered",
		"SELECT domain, finding_type, title, detail, severity, source_module, discovered FROM odint_findings ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e, f, g string
			r.Scan(&a, &b, &c, &d, &e, &f, &g)
			return fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s", a, b, csvEsc(c), csvEsc(d), e, f, g)
		})

	// Technologies
	total += exportCSV("technologies", "domain,tech_name,tech_category,confidence,discovered",
		"SELECT domain, tech_name, tech_category, confidence, discovered FROM odint_technologies ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, e string
			var d int
			r.Scan(&a, &b, &c, &d, &e)
			return fmt.Sprintf("%s,%s,%s,%d,%s", a, b, c, d, e)
		})

	// DNS
	total += exportCSV("dns", "domain,record_type,record_value,discovered",
		"SELECT domain, record_type, record_value, discovered FROM odint_dns ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d string
			r.Scan(&a, &b, &c, &d)
			return fmt.Sprintf("%s,%s,%s,%s", a, b, csvEsc(c), d)
		})

	// SSL
	total += exportCSV("ssl", "domain,issuer,subject,serial,not_before,not_after,sig_algorithm,key_size,san_names,is_expired,discovered",
		"SELECT domain, issuer, subject, serial, not_before, not_after, sig_algorithm, key_size, san_names, is_expired, discovered FROM odint_ssl ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e, f, g, i, k string
			var h, j int
			r.Scan(&a, &b, &c, &d, &e, &f, &g, &h, &i, &j, &k)
			return fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%d,%s,%d,%s", a, csvEsc(b), csvEsc(c), d, e, f, g, h, csvEsc(i), j, k)
		})

	// Nmap results
	total += exportCSV("nmap", "domain,port,protocol,state,service,version,product,discovered",
		"SELECT domain, port, protocol, state, service, version, product, discovered FROM odint_nmap_results ORDER BY domain, port",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, c, d, e, f, g, h string
			var b int
			r.Scan(&a, &b, &c, &d, &e, &f, &g, &h)
			return fmt.Sprintf("%s,%d,%s,%s,%s,%s,%s,%s", a, b, c, d, csvEsc(e), csvEsc(f), csvEsc(g), h)
		})

	// CVE findings
	total += exportCSV("cves", "domain,cve_id,severity,score,description,service,discovered",
		"SELECT domain, cve_id, severity, score, description, service, discovered FROM odint_cve_findings ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, e, f, g string
			var d float64
			r.Scan(&a, &b, &c, &d, &e, &f, &g)
			return fmt.Sprintf("%s,%s,%s,%.1f,%s,%s,%s", a, b, c, d, csvEsc(e), csvEsc(f), g)
		})

	// Subdomains
	total += exportCSV("subdomains", "domain,subdomain,source,ip,status,discovered",
		"SELECT domain, subdomain, source, ip, status, discovered FROM odint_subdomains ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, f string
			var e int
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%d,%s", a, b, c, d, e, f)
		})

	// Exposed paths
	total += exportCSV("paths", "domain,path,status_code,path_type,discovered",
		"SELECT domain, path, status_code, path_type, discovered FROM odint_exposed_paths ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, d, e string
			var c int
			r.Scan(&a, &b, &c, &d, &e)
			return fmt.Sprintf("%s,%s,%d,%s,%s", a, b, c, d, e)
		})

	// Secrets
	total += exportCSV("secrets", "domain,secret_type,value,location,severity,discovered",
		"SELECT domain, secret_type, value, location, severity, discovered FROM odint_secrets ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e, f string
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%s,%s", a, b, csvEsc(c), csvEsc(d), e, f)
		})

	// JWT tokens
	total += exportCSV("jwt", "domain,algorithm,issuer,subject,location,discovered",
		"SELECT domain, algorithm, issuer, subject, location, discovered FROM odint_jwt_tokens ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e, f string
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%s,%s", a, csvEsc(b), csvEsc(c), csvEsc(d), csvEsc(e), f)
		})

	// GraphQL endpoints
	total += exportCSV("graphql", "domain,url,introspection,types_count,discovered",
		"SELECT domain, url, introspection, types_count, discovered FROM odint_graphql_endpoints ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, e string
			var c, d int
			r.Scan(&a, &b, &c, &d, &e)
			return fmt.Sprintf("%s,%s,%d,%d,%s", a, b, c, d, e)
		})

	// WordPress sites
	total += exportCSV("wordpress", "domain,version,theme,is_wp,discovered",
		"SELECT domain, version, theme, is_wp, discovered FROM odint_wordpress_sites ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, e string
			var d int
			r.Scan(&a, &b, &c, &d, &e)
			return fmt.Sprintf("%s,%s,%s,%d,%s", a, csvEsc(b), csvEsc(c), d, e)
		})

	// Social profiles
	total += exportCSV("social", "domain,platform,url,username,exists,discovered",
		"SELECT domain, platform, url, username, exists, discovered FROM odint_social_profiles ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, f string
			var e int
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%d,%s", a, b, csvEsc(c), csvEsc(d), e, f)
		})

	// HIBP breaches
	total += exportCSV("hibp", "domain,email,breach_name,breach_date,pwn_count,discovered",
		"SELECT domain, email, breach_name, breach_date, pwn_count, discovered FROM odint_hibp_breaches ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, f string
			var e int
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%d,%s", a, csvEsc(b), csvEsc(c), d, e, f)
		})

	// Cloud exposures
	total += exportCSV("cloud", "domain,provider,bucket_name,url,accessible,discovered",
		"SELECT domain, provider, bucket_name, url, accessible, discovered FROM odint_cloud_exposures ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, f string
			var e int
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%d,%s", a, b, csvEsc(c), csvEsc(d), e, f)
		})

	// Network intel
	total += exportCSV("netintel", "domain,asn,asn_org,bgp_prefix,country,city,isp,discovered",
		"SELECT domain, asn, asn_org, bgp_prefix, country, city, isp, discovered FROM odint_network_intel ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e, f, g, h string
			r.Scan(&a, &b, &c, &d, &e, &f, &g, &h)
			return fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", a, csvEsc(b), csvEsc(c), d, e, f, csvEsc(g), h)
		})

	// Discovered files
	total += exportCSV("files", "domain,url,filename,file_type,source,discovered",
		"SELECT domain, url, filename, file_type, source, discovered FROM odint_discovered_files ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e, f string
			r.Scan(&a, &b, &c, &d, &e, &f)
			return fmt.Sprintf("%s,%s,%s,%s,%s,%s", a, csvEsc(b), csvEsc(c), d, e, f)
		})

	// Email security
	total += exportCSV("emailsec", "domain,mta_sts,bimi,smtp_tls_rpt,discovered",
		"SELECT domain, mta_sts_policy, bimi_record, smtp_tls_rpt, discovered FROM odint_email_security ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, e string
			r.Scan(&a, &b, &c, &d, &e)
			return fmt.Sprintf("%s,%s,%s,%s,%s", a, csvEsc(b), csvEsc(c), csvEsc(d), e)
		})

	// Virtual hosts
	total += exportCSV("vhosts", "domain,vhost_domain,ip,source,is_default,status_code,discovered",
		"SELECT domain, vhost_domain, ip, source, is_default, status_code, discovered FROM odint_virtual_hosts ORDER BY domain",
		func(r interface{ Scan(...interface{}) error }) string {
			var a, b, c, d, g string
			var e, f int
			r.Scan(&a, &b, &c, &d, &e, &f, &g)
			return fmt.Sprintf("%s,%s,%s,%s,%d,%d,%s", a, b, c, d, e, f, g)
		})

	tui.addOutput("")
	tui.addOutput(fmt.Sprintf("[+] === ODINT Export Complete: %d total records across all tables ===", total))
	tui.addOutput(fmt.Sprintf("[+] Export directory: %s", tui.logsDir))
	tui.addOutput("")
}

// ===========================================================================
// HELPER — Load domains from file
// ===========================================================================

func odintLoadDomainsFromFile(filePath string) []string {
	f, err := os.Open(filePath)
	if err != nil {
		return nil
	}
	defer f.Close()

	var domains []string
	seen := make(map[string]bool)
	scanner := newOdintLineScanner(f)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
			continue
		}
		// Strip protocol
		line = strings.TrimPrefix(line, "https://")
		line = strings.TrimPrefix(line, "http://")
		line = strings.TrimSuffix(line, "/")
		line = strings.TrimPrefix(line, "www.")
		if line != "" && !seen[line] {
			seen[line] = true
			domains = append(domains, line)
		}
	}
	return domains
}

// odintLineScanner provides simple line-by-line reading.
type odintLineScanner struct {
	lines []string
	pos   int
	cur   string
}

func newOdintLineScanner(f *os.File) *odintLineScanner {
	data, err := io.ReadAll(f)
	if err != nil {
		return &odintLineScanner{lines: nil, pos: 0}
	}
	return &odintLineScanner{lines: strings.Split(string(data), "\n"), pos: -1}
}

func (s *odintLineScanner) Scan() bool {
	s.pos++
	if s.pos >= len(s.lines) {
		return false
	}
	s.cur = strings.TrimRight(s.lines[s.pos], "\r")
	return true
}

func (s *odintLineScanner) Text() string {
	return s.cur
}

// ===========================================================================
// FILE PICKER HANDLER — runOdintBulkFromFile
// ===========================================================================

func (tui *TUI) runOdintBulkFromFile(ctx context.Context, filePath string) {
	domains := odintLoadDomainsFromFile(filePath)
	if len(domains) == 0 {
		tui.addOutput("[-] No domains found in file")
		return
	}
	tui.addOutput(fmt.Sprintf("[*] Loaded %d domains from %s", len(domains), filepath.Base(filePath)))
	tui.runOdintBulkRecon(ctx, domains)
}

// ===========================================================================
// DASHBOARD — renderODINTDashboard
// ===========================================================================

func (tui *TUI) renderODINTDashboard() {
	// Red theme — matches standalone ODINT Toolkit
	borderStyle := tcell.StyleDefault.Foreground(tcell.ColorDarkRed)
	textStyle := tcell.StyleDefault.Foreground(tcell.ColorWhite)
	highlightStyle := tcell.StyleDefault.Foreground(tcell.ColorOrangeRed)
	successStyle := tcell.StyleDefault.Foreground(tcell.ColorLime)
	dimStyle := tcell.StyleDefault.Foreground(tcell.ColorGray)
	warnStyle := tcell.StyleDefault.Foreground(tcell.ColorYellow)
	titleStyle := tcell.StyleDefault.Foreground(tcell.ColorRed).Bold(true)
	inputStyle := tcell.StyleDefault.Foreground(tcell.ColorOrange)

	// Layout
	menuWidth := 48
	if menuWidth > tui.width/2 {
		menuWidth = tui.width / 2
	}
	outputX := menuWidth + 1
	outputWidth := tui.width - menuWidth - 2
	inputHeight := 3
	outputHeight := tui.height - inputHeight - 2

	// Header
	header := fmt.Sprintf(" ODINT Toolkit v4.0.0 ")
	tui.drawString(1, 0, header, titleStyle)

	// DB stats in header — matches standalone format
	var domainCount, techCount, emailCount, subCount, secretCount, findingCount, cveCount, portCount int
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_scans").Scan(&domainCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_technologies").Scan(&techCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_emails").Scan(&emailCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_subdomains").Scan(&subCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_secrets").Scan(&secretCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_findings").Scan(&findingCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_cves").Scan(&cveCount)
	tui.db.conn.QueryRow("SELECT COUNT(*) FROM odint_ports").Scan(&portCount)

	// Build status bar with scan progress when active
	var statsStr string
	progress := atomic.LoadInt64(&tui.odintProgress)
	total := atomic.LoadInt64(&tui.odintTotal)
	if total > 0 {
		statsStr = fmt.Sprintf("[%d/%d] ", progress, total)
	}
	statsStr += fmt.Sprintf("Domains:%d Techs:%d Emails:%d Subs:%d Secrets:%d Findings:%d CVEs:%d Ports:%d",
		domainCount, techCount, emailCount, subCount, secretCount, findingCount, cveCount, portCount)

	maxStats := tui.width - len(header) - 2
	if len(statsStr) > maxStats && maxStats > 0 {
		statsStr = statsStr[:maxStats]
	}
	statsX := tui.width - len(statsStr) - 1
	if statsX < len(header)+1 {
		statsX = len(header) + 1
	}
	tui.drawStringClipped(statsX, 0, tui.width-statsX, statsStr, dimStyle)

	// Left panel — COMMANDS
	tui.drawBox(0, 1, menuWidth, tui.height-inputHeight-1, "COMMANDS", borderStyle)

	menuY := 3
	items := tui.menuItems
	for i, item := range items {
		y := menuY + i
		if y >= tui.height-inputHeight-2 {
			break
		}
		keyStyle := highlightStyle
		nameStyle := textStyle
		if i == tui.selectedItem && !tui.focusOutput {
			tui.fillRect(1, y, menuWidth-2, 1, ' ', tcell.StyleDefault.Background(tcell.ColorDarkRed))
			keyStyle = successStyle.Bold(true).Background(tcell.ColorDarkRed)
			nameStyle = tcell.StyleDefault.Foreground(tcell.ColorWhite).Bold(true).Background(tcell.ColorDarkRed)
		}
		tui.drawString(2, y, fmt.Sprintf("[%s]", item.Key), keyStyle)
		tui.drawStringClipped(6, y, menuWidth-8, item.Name+" - "+item.Desc, nameStyle)
	}

	// Version info below menu items — matches standalone
	infoY := menuY + len(items) + 1
	if infoY < tui.height-5 {
		tui.drawString(2, infoY+1, "Auto-runs ALL 42 recon modules", dimStyle)
		tui.drawString(2, infoY+2, "for every target domain.", dimStyle)
	}

	// Right panel — OUTPUT
	tui.drawBox(outputX, 1, outputWidth, outputHeight, "OUTPUT", borderStyle)

	tui.outputMutex.Lock()
	maxLines := outputHeight - 2
	totalLines := len(tui.outputLines)

	if totalLines == 0 {
		tui.drawStringClipped(outputX+2, 3, outputWidth-4, "[*] ODINT Toolkit ready -- Select a command to begin", highlightStyle)
	} else {
		startLine := tui.outputScroll
		for i := 0; i < maxLines && startLine+i < totalLines; i++ {
			line := tui.outputLines[startLine+i]
			y := 3 + i

			lineStyle := textStyle
			if strings.HasPrefix(line, "[+]") {
				lineStyle = successStyle
			} else if strings.HasPrefix(line, "[-]") {
				lineStyle = tcell.StyleDefault.Foreground(tcell.ColorRed)
			} else if strings.HasPrefix(line, "[*]") {
				lineStyle = highlightStyle
			} else if strings.HasPrefix(line, "[!]") {
				lineStyle = warnStyle
			} else if strings.HasPrefix(line, "    ") {
				lineStyle = dimStyle
			}

			maxW := outputWidth - 3
			tui.drawStringClipped(outputX+1, y, maxW, line, lineStyle)
		}

		if totalLines > maxLines {
			scrollInfo := fmt.Sprintf("[%d/%d]", totalLines-tui.outputScroll, totalLines)
			tui.drawString(outputX+outputWidth-len(scrollInfo)-2, tui.height-inputHeight-2, scrollInfo, dimStyle)
		}
	}
	tui.outputMutex.Unlock()

	// Input bar at bottom — matches standalone minimal style
	inputY := tui.height - 2
	for x := 0; x < tui.width; x++ {
		tui.screen.SetContent(x, inputY-1, '\u2500', nil, borderStyle)
	}

	if tui.commandState == StateInput {
		prompt := tui.inputPrompt + ": "
		tui.drawString(1, inputY, prompt, highlightStyle)
		tui.drawString(1+len(prompt), inputY, tui.inputBuffer, inputStyle)
		tui.screen.ShowCursor(1+len(prompt)+tui.inputCursor, inputY)
	} else if tui.commandState == StateRunning {
		domain := tui.odintCurrentDomain
		runMsg := fmt.Sprintf("[%d/%d] Scanning %s... press ESC to cancel", progress, total, domain)
		tui.drawString(1, inputY, runMsg, warnStyle)
		tui.screen.HideCursor()
	} else if tui.commandState == StateComplete {
		tui.drawString(1, inputY, "[+] Complete! Press any key to continue", successStyle)
		tui.screen.HideCursor()
	} else {
		tui.drawString(1, inputY, "> Use UP/DOWN to navigate, ENTER to select | Tab: Module Switch", dimStyle)
		tui.screen.HideCursor()
	}
}

// ===========================================================================
// PHASE 2: ENHANCED DNS — SOA, SRV, CAA, DKIM via stdlib
// ===========================================================================

func odintDNSEnhanced(ctx context.Context, domain string) []OdintDNSRecord {
	var records []OdintDNSRecord

	// SOA — try net.LookupNS and construct from it (stdlib has no direct SOA)
	// We'll use a TXT lookup on the SOA prefix as a fallback
	nss, _ := net.LookupNS(domain)
	if len(nss) > 0 {
		records = append(records, OdintDNSRecord{Type: "SOA", Value: fmt.Sprintf("ns=%s", nss[0].Host)})
	}

	// SRV records for common services
	srvTargets := []string{
		"_http._tcp", "_https._tcp", "_sip._tcp", "_sip._udp",
		"_xmpp-server._tcp", "_xmpp-client._tcp", "_autodiscover._tcp",
		"_imap._tcp", "_imaps._tcp", "_pop3._tcp", "_submission._tcp",
	}
	for _, srvPrefix := range srvTargets {
		if ctx.Err() != nil {
			break
		}
		_, addrs, err := net.LookupSRV("", "", srvPrefix+"."+domain)
		if err != nil || len(addrs) == 0 {
			continue
		}
		for _, srv := range addrs {
			records = append(records, OdintDNSRecord{
				Type:  "SRV",
				Value: fmt.Sprintf("%s.%s -> %s:%d (pri:%d w:%d)", srvPrefix, domain, srv.Target, srv.Port, srv.Priority, srv.Weight),
			})
		}
	}

	// CAA — use TXT lookup with type prefix (stdlib doesn't support CAA directly)
	// Some resolvers return CAA as TXT for the domain; we check common patterns
	resolver := &net.Resolver{}
	lookupCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	txts, _ := resolver.LookupTXT(lookupCtx, domain)
	for _, txt := range txts {
		if strings.Contains(txt, "issue") || strings.Contains(txt, "issuewild") || strings.Contains(txt, "iodef") {
			records = append(records, OdintDNSRecord{Type: "CAA", Value: txt})
		}
	}

	// DKIM — check common selectors
	selectors := []string{"default", "google", "selector1", "selector2", "k1", "mail", "dkim"}
	for _, sel := range selectors {
		if ctx.Err() != nil {
			break
		}
		dkimDomain := fmt.Sprintf("%s._domainkey.%s", sel, domain)
		dkimCtx, dkimCancel := context.WithTimeout(ctx, 3*time.Second)
		dkimTxts, err := resolver.LookupTXT(dkimCtx, dkimDomain)
		dkimCancel()
		if err != nil {
			continue
		}
		for _, txt := range dkimTxts {
			if strings.Contains(txt, "v=DKIM1") {
				records = append(records, OdintDNSRecord{Type: "DKIM", Value: fmt.Sprintf("[%s] %s", sel, txt)})
				break
			}
		}
	}

	return records
}

// ===========================================================================
// PHASE 2: ENHANCED SSL — cipher suites, OCSP, PFS, ALPN
// ===========================================================================

var odintWeakCipherSuites = map[uint16]string{
	0x0004: "TLS_RSA_WITH_RC4_128_MD5",
	0x0005: "TLS_RSA_WITH_RC4_128_SHA",
	0x000A: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
	0x002F: "TLS_RSA_WITH_AES_128_CBC_SHA",
	0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA",
	0xC007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
	0xC011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
	0xC012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
}

type OdintSSLDeepResult struct {
	CipherSuites      []string
	WeakCiphers       []string
	OCSPStapling      bool
	CTSCTs            int
	PFS               bool
	ALPNProtocols     []string
	SessionResumption bool
	HSTSPreload       bool
	KeyExchange       string
	PublicKeyAlgo     string
	PublicKeySize     int
}

func odintSSLDeepAnalysis(ctx context.Context, domain string) *OdintSSLDeepResult {
	result := &OdintSSLDeepResult{}
	host := domain
	if !strings.Contains(host, ":") {
		host = domain + ":443"
	}

	// Main connection for detailed analysis
	dialer := &net.Dialer{Timeout: 10 * time.Second}
	conn, err := tls.DialWithDialer(dialer, "tcp", host, &tls.Config{InsecureSkipVerify: true})
	if err != nil {
		return result
	}
	state := conn.ConnectionState()
	conn.Close()

	// Cipher suite
	cipherName := tls.CipherSuiteName(state.CipherSuite)
	if cipherName != "" {
		result.CipherSuites = append(result.CipherSuites, cipherName)
		result.KeyExchange = cipherName
	}

	// Weak cipher check
	if _, weak := odintWeakCipherSuites[state.CipherSuite]; weak {
		result.WeakCiphers = append(result.WeakCiphers, cipherName)
	}

	// OCSP stapling
	result.OCSPStapling = len(state.OCSPResponse) > 0

	// CT SCTs
	result.CTSCTs = len(state.SignedCertificateTimestamps)

	// PFS detection
	result.PFS = strings.Contains(cipherName, "ECDHE") || strings.Contains(cipherName, "DHE")

	// ALPN
	if state.NegotiatedProtocol != "" {
		result.ALPNProtocols = append(result.ALPNProtocols, state.NegotiatedProtocol)
	}

	// Check additional ALPN protocols
	for _, proto := range []string{"h2", "http/1.1"} {
		if ctx.Err() != nil {
			break
		}
		testConn, err := tls.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, "tcp", host,
			&tls.Config{InsecureSkipVerify: true, NextProtos: []string{proto}})
		if err != nil {
			continue
		}
		negotiated := testConn.ConnectionState().NegotiatedProtocol
		testConn.Close()
		if negotiated == proto {
			found := false
			for _, existing := range result.ALPNProtocols {
				if existing == proto {
					found = true
					break
				}
			}
			if !found {
				result.ALPNProtocols = append(result.ALPNProtocols, proto)
			}
		}
	}

	// Session resumption
	result.SessionResumption = state.DidResume

	// Enumerate cipher suites across TLS versions
	versions := []struct {
		version uint16
		name    string
	}{
		{tls.VersionTLS10, "TLS 1.0"},
		{tls.VersionTLS11, "TLS 1.1"},
		{tls.VersionTLS12, "TLS 1.2"},
		{tls.VersionTLS13, "TLS 1.3"},
	}
	for _, v := range versions {
		if ctx.Err() != nil {
			break
		}
		testConn, err := tls.DialWithDialer(&net.Dialer{Timeout: 3 * time.Second}, "tcp", host,
			&tls.Config{InsecureSkipVerify: true, MinVersion: v.version, MaxVersion: v.version})
		if err != nil {
			continue
		}
		cs := tls.CipherSuiteName(testConn.ConnectionState().CipherSuite)
		testConn.Close()
		if cs != "" {
			found := false
			for _, existing := range result.CipherSuites {
				if existing == cs {
					found = true
					break
				}
			}
			if !found {
				result.CipherSuites = append(result.CipherSuites, cs)
			}
			if _, weak := odintWeakCipherSuites[testConn.ConnectionState().CipherSuite]; weak {
				result.WeakCiphers = append(result.WeakCiphers, fmt.Sprintf("%s (%s)", cs, v.name))
			}
			if v.version <= tls.VersionTLS11 {
				result.WeakCiphers = append(result.WeakCiphers, fmt.Sprintf("%s (deprecated)", v.name))
			}
		}
	}

	// HSTS Preload check
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(fmt.Sprintf("https://hstspreload.org/api/v2/status?domain=%s", domain))
	if err == nil {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 10*1024))
		resp.Body.Close()
		var preloadResult struct {
			Status string `json:"status"`
		}
		if json.Unmarshal(body, &preloadResult) == nil {
			result.HSTSPreload = preloadResult.Status == "preloaded"
		}
	}

	return result
}

// ===========================================================================
// PHASE 2: ENHANCED HTTP/CONTENT — CORS, cookies, redirects, forms, meta
// ===========================================================================

func odintAnalyzeCORS(headers map[string]string) []string {
	var findings []string
	if acao, ok := headers["Access-Control-Allow-Origin"]; ok {
		if acao == "*" {
			findings = append(findings, "CORS: Wildcard origin (*) allowed")
		}
		if strings.Contains(acao, "null") {
			findings = append(findings, "CORS: Null origin allowed (potential bypass)")
		}
	}
	if acac, ok := headers["Access-Control-Allow-Credentials"]; ok {
		if strings.ToLower(acac) == "true" {
			if acao, ok2 := headers["Access-Control-Allow-Origin"]; ok2 && acao == "*" {
				findings = append(findings, "CORS: Credentials with wildcard origin (misconfiguration)")
			}
		}
	}
	return findings
}

func odintAnalyzeCookiesDetailed(cookies []*http.Cookie) []OdintCookieInfo {
	var results []OdintCookieInfo
	for _, c := range cookies {
		info := OdintCookieInfo{
			Name:     c.Name,
			Value:    c.Value,
			Domain:   c.Domain,
			Path:     c.Path,
			Secure:   c.Secure,
			HttpOnly: c.HttpOnly,
			SameSite: fmt.Sprintf("%d", c.SameSite),
		}
		if !c.Expires.IsZero() {
			info.Expires = c.Expires.Format("2006-01-02")
		}
		results = append(results, info)
	}
	return results
}

func odintTrackRedirects(client *http.Client, targetURL string) []string {
	var chain []string
	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		chain = append(chain, req.URL.String())
		if len(via) >= 10 {
			return fmt.Errorf("too many redirects")
		}
		return nil
	}
	return chain
}

func odintExtractMetaTags(body string) map[string]string {
	tags := make(map[string]string)
	metaRe := regexp.MustCompile(`<meta\s+(?:[^>]*?)(?:name|property)=["']([^"']+)["']\s+content=["']([^"']+)["']`)
	metaRe2 := regexp.MustCompile(`<meta\s+content=["']([^"']+)["']\s+(?:name|property)=["']([^"']+)["']`)

	for _, match := range metaRe.FindAllStringSubmatch(body, -1) {
		if len(match) > 2 {
			tags[match[1]] = match[2]
		}
	}
	for _, match := range metaRe2.FindAllStringSubmatch(body, -1) {
		if len(match) > 2 {
			tags[match[2]] = match[1]
		}
	}
	return tags
}

func odintExtractForms(body string) []OdintFormInfo {
	var forms []OdintFormInfo
	formRe := regexp.MustCompile(`(?is)<form[^>]*>(.*?)</form>`)
	actionRe := regexp.MustCompile(`action=["']([^"']*)["']`)
	methodRe := regexp.MustCompile(`method=["']([^"']*)["']`)
	inputRe := regexp.MustCompile(`<input[^>]*(?:name|type)=["']([^"']*)["'][^>]*>`)

	for _, match := range formRe.FindAllStringSubmatch(body, 20) {
		if len(match) < 2 {
			continue
		}
		formHTML := match[0]
		info := OdintFormInfo{Method: "GET"}

		if am := actionRe.FindStringSubmatch(formHTML); len(am) > 1 {
			info.Action = am[1]
		}
		if mm := methodRe.FindStringSubmatch(formHTML); len(mm) > 1 {
			info.Method = strings.ToUpper(mm[1])
		}

		for _, im := range inputRe.FindAllStringSubmatch(match[1], -1) {
			if len(im) > 1 {
				info.Fields = append(info.Fields, im[1])
			}
		}

		// Classify form type
		formLower := strings.ToLower(match[1])
		if strings.Contains(formLower, "password") || strings.Contains(formLower, "login") {
			info.Type = "login"
		} else if strings.Contains(formLower, "search") {
			info.Type = "search"
		} else if strings.Contains(formLower, "email") && strings.Contains(formLower, "subscribe") {
			info.Type = "signup"
		} else if strings.Contains(formLower, "contact") || strings.Contains(formLower, "message") {
			info.Type = "contact"
		} else {
			info.Type = "other"
		}

		forms = append(forms, info)
	}
	return forms
}

func odintExtractSchemaOrg(body string) []OdintStructuredDataItem {
	var items []OdintStructuredDataItem

	// JSON-LD
	ldRe := regexp.MustCompile(`(?is)<script\s+type=["']application/ld\+json["'][^>]*>(.*?)</script>`)
	for _, match := range ldRe.FindAllStringSubmatch(body, 10) {
		if len(match) < 2 {
			continue
		}
		var data map[string]interface{}
		if json.Unmarshal([]byte(match[1]), &data) == nil {
			props := make(map[string]string)
			for k, v := range data {
				props[k] = fmt.Sprintf("%v", v)
			}
			typeName := ""
			if t, ok := data["@type"]; ok {
				typeName = fmt.Sprintf("%v", t)
			}
			items = append(items, OdintStructuredDataItem{
				Type:       "json-ld",
				ItemType:   typeName,
				Properties: props,
			})
		}
	}
	return items
}

func odintExtractAnalyticsIDs(body string) []OdintAnalyticsID {
	var ids []OdintAnalyticsID
	patterns := map[string]*regexp.Regexp{
		"Google Analytics (UA)": regexp.MustCompile(`UA-\d{4,10}-\d{1,4}`),
		"Google Analytics (G)":  regexp.MustCompile(`G-[A-Z0-9]{10,15}`),
		"Google Tag Manager":    regexp.MustCompile(`GTM-[A-Z0-9]{4,10}`),
		"Facebook Pixel":        regexp.MustCompile(`fbq\(['"]init['"],\s*['"](\d{10,20})['"]`),
		"Hotjar":                regexp.MustCompile(`hjid:\s*(\d{5,10})`),
		"Clarity":               regexp.MustCompile(`clarity\s*\(\s*["']set["'],\s*["']([a-z0-9]+)["']`),
	}

	seen := make(map[string]bool)
	for platform, re := range patterns {
		for _, match := range re.FindAllStringSubmatch(body, 5) {
			val := match[0]
			if len(match) > 1 {
				val = match[1]
			}
			key := platform + ":" + val
			if !seen[key] {
				seen[key] = true
				ids = append(ids, OdintAnalyticsID{Platform: platform, ID: val})
			}
		}
	}
	return ids
}

func odintExtractHreflang(body string) []string {
	var tags []string
	re := regexp.MustCompile(`<link[^>]+hreflang=["']([^"']+)["'][^>]+href=["']([^"']+)["']`)
	for _, match := range re.FindAllStringSubmatch(body, 50) {
		if len(match) > 2 {
			tags = append(tags, fmt.Sprintf("%s: %s", match[1], match[2]))
		}
	}
	return tags
}

func odintExtractCanonical(body string) string {
	re := regexp.MustCompile(`<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']+)["']`)
	if match := re.FindStringSubmatch(body); len(match) > 1 {
		return match[1]
	}
	return ""
}

func odintExtractFeeds(body string) []string {
	var feeds []string
	re := regexp.MustCompile(`<link[^>]+type=["'](application/rss\+xml|application/atom\+xml)["'][^>]+href=["']([^"']+)["']`)
	for _, match := range re.FindAllStringSubmatch(body, 10) {
		if len(match) > 2 {
			feeds = append(feeds, match[2])
		}
	}
	return feeds
}

// ===========================================================================
// PHASE 3: NMAP SERVICE SCANNING
// ===========================================================================

func odintNmapAvailable() bool {
	name := "nmap"
	if runtime.GOOS == "windows" {
		name = "nmap.exe"
	}
	_, err := exec.LookPath(name)
	return err == nil
}

func odintRunNmap(ctx context.Context, hostname string) ([]OdintNmapPort, error) {
	if !odintNmapAvailable() {
		return nil, fmt.Errorf("nmap not installed")
	}

	host := hostname
	for _, prefix := range []string{"https://", "http://"} {
		host = strings.TrimPrefix(host, prefix)
	}
	host = strings.TrimSuffix(host, "/")
	if idx := strings.LastIndex(host, ":"); idx > 0 {
		if _, err := strconv.Atoi(host[idx+1:]); err == nil {
			host = host[:idx]
		}
	}

	nmapCtx, cancel := context.WithTimeout(ctx, 120*time.Second)
	defer cancel()

	args := []string{"-sCV", "-n", "-T4", "--top-ports", "1000", "-oX", "-", host}
	cmd := exec.CommandContext(nmapCtx, "nmap", args...)
	output, err := cmd.Output()
	if err != nil {
		if strings.Contains(err.Error(), "permission") || strings.Contains(err.Error(), "root") {
			args[0] = "-sTV"
			cmd = exec.CommandContext(nmapCtx, "nmap", args...)
			output, err = cmd.Output()
			if err != nil {
				return nil, fmt.Errorf("nmap failed: %w", err)
			}
		} else {
			return nil, fmt.Errorf("nmap failed: %w", err)
		}
	}

	var run odintNmapRun
	if err := xml.Unmarshal(output, &run); err != nil {
		return nil, fmt.Errorf("nmap XML parse error: %w", err)
	}

	var results []OdintNmapPort
	for _, h := range run.Hosts {
		for _, p := range h.Ports.Port {
			if p.State.State != "open" {
				continue
			}
			version := p.Service.Version
			if version == "" && p.Service.Product != "" {
				version = p.Service.Product
			}
			results = append(results, OdintNmapPort{
				Port:     p.PortID,
				Protocol: p.Protocol,
				State:    p.State.State,
				Service:  p.Service.Name,
				Version:  version,
				Product:  p.Service.Product,
				Extra:    p.Service.ExtraInfo,
			})
		}
	}
	return results, nil
}

func (tui *TUI) odintStoreNmap(domain string, ports []OdintNmapPort) {
	for _, p := range ports {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_nmap_results
			(domain, port, protocol, state, service, version, product, extra, discovered)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, p.Port, p.Protocol, p.State, p.Service, p.Version, p.Product, p.Extra)
	}
}

// ===========================================================================
// PHASE 3: CVE VULNERABILITY LOOKUP
// ===========================================================================

func odintLookupCVEs(ctx context.Context, product, version, userAgent string) ([]OdintCVEInfo, error) {
	if product == "" || version == "" {
		return nil, nil
	}

	keyword := strings.TrimSpace(product + " " + version)
	apiURL := fmt.Sprintf("https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=%s&resultsPerPage=10",
		url.QueryEscape(keyword))

	client := &http.Client{Timeout: 15 * time.Second}
	req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", userAgent)

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("NVD API request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == 403 || resp.StatusCode == 429 {
		return nil, fmt.Errorf("NVD rate limited (HTTP %d)", resp.StatusCode)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("NVD API returned HTTP %d", resp.StatusCode)
	}

	body, err := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024))
	if err != nil {
		return nil, err
	}

	var nvdResp odintNVDResponse
	if err := json.Unmarshal(body, &nvdResp); err != nil {
		return nil, fmt.Errorf("NVD parse error: %w", err)
	}

	var results []OdintCVEInfo
	for _, vuln := range nvdResp.Vulns {
		cve := OdintCVEInfo{ID: vuln.CVE.ID}
		for _, desc := range vuln.CVE.Descriptions {
			if desc.Lang == "en" {
				d := desc.Value
				if len(d) > 200 {
					d = d[:197] + "..."
				}
				cve.Description = d
				break
			}
		}
		if len(vuln.CVE.Metrics.CvssV31) > 0 {
			cve.Score = vuln.CVE.Metrics.CvssV31[0].CvssData.BaseScore
			cve.Severity = vuln.CVE.Metrics.CvssV31[0].CvssData.BaseSeverity
		} else if len(vuln.CVE.Metrics.CvssV2) > 0 {
			cve.Score = vuln.CVE.Metrics.CvssV2[0].CvssData.BaseScore
			cve.Severity = vuln.CVE.Metrics.CvssV2[0].CvssData.BaseSeverity
		}
		if cve.Severity == "" {
			switch {
			case cve.Score >= 9.0:
				cve.Severity = "CRITICAL"
			case cve.Score >= 7.0:
				cve.Severity = "HIGH"
			case cve.Score >= 4.0:
				cve.Severity = "MEDIUM"
			default:
				cve.Severity = "LOW"
			}
		}
		results = append(results, cve)
	}
	return results, nil
}

func odintEnrichNmapWithCVEs(ctx context.Context, ports []OdintNmapPort, userAgent string) []OdintNmapPort {
	for i := range ports {
		if ctx.Err() != nil {
			break
		}
		if ports[i].Product == "" || ports[i].Version == "" {
			continue
		}
		cves, err := odintLookupCVEs(ctx, ports[i].Product, ports[i].Version, userAgent)
		if err != nil {
			continue
		}
		ports[i].CVEs = cves
		time.Sleep(6500 * time.Millisecond) // NVD rate limit: 5/30s
	}
	return ports
}

func (tui *TUI) odintStoreCVEs(domain string, ports []OdintNmapPort) {
	for _, p := range ports {
		for _, cve := range p.CVEs {
			tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_cve_findings
				(domain, service, product, version, cve_id, cvss_score, severity, description, discovered)
				VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
				domain, p.Service, p.Product, p.Version, cve.ID, cve.Score, cve.Severity, cve.Description)
		}
	}
}

// ===========================================================================
// PHASE 3: WORDPRESS ENUMERATION
// ===========================================================================

func odintCheckWordPress(ctx context.Context, client *http.Client, baseURL string) (
	isWP bool, version, theme string, users []OdintWPUser, plugins []string, restData *OdintWPRESTData,
) {
	// Check wp-json
	req, _ := http.NewRequestWithContext(ctx, "GET", baseURL+"/wp-json/", nil)
	req.Header.Set("User-Agent", UserAgent)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return
	}

	rootBody, _ := io.ReadAll(io.LimitReader(resp.Body, 100*1024))
	resp.Body.Close()
	isWP = true

	var rootAPI struct {
		Namespaces  []string `json:"namespaces"`
		Name        string   `json:"name"`
		Description string   `json:"description"`
	}
	if json.Unmarshal(rootBody, &rootAPI) == nil {
		restData = &OdintWPRESTData{Namespaces: rootAPI.Namespaces}

		// Detect plugins from namespaces
		nsPlugins := map[string]string{
			"wc/": "WooCommerce", "yoast": "Yoast SEO", "acf": "ACF",
			"wpml": "WPML", "jetpack": "Jetpack", "rankmath": "Rank Math",
			"contact-form-7": "Contact Form 7", "gravityforms": "Gravity Forms",
			"wpforms": "WPForms", "elementor": "Elementor",
		}
		for _, ns := range rootAPI.Namespaces {
			nsLower := strings.ToLower(ns)
			for key, name := range nsPlugins {
				if strings.Contains(nsLower, key) {
					restData.Plugins = appendOdintUnique(restData.Plugins, name)
				}
			}
		}
		plugins = restData.Plugins
	}

	// Enumerate users
	req2, _ := http.NewRequestWithContext(ctx, "GET", baseURL+"/wp-json/wp/v2/users?per_page=100", nil)
	req2.Header.Set("User-Agent", UserAgent)
	resp2, err := client.Do(req2)
	if err == nil && resp2.StatusCode == 200 {
		var rawUsers []struct {
			ID         int               `json:"id"`
			Name       string            `json:"name"`
			Slug       string            `json:"slug"`
			Link       string            `json:"link"`
			AvatarURLs map[string]string `json:"avatar_urls"`
		}
		if json.NewDecoder(resp2.Body).Decode(&rawUsers) == nil {
			hashRe := regexp.MustCompile(`/avatar/([a-f0-9]{32})`)
			for _, u := range rawUsers {
				wpUser := OdintWPUser{
					ID: u.ID, Username: u.Slug, DisplayName: u.Name, URL: u.Link,
				}
				if av96, ok := u.AvatarURLs["96"]; ok {
					if m := hashRe.FindStringSubmatch(av96); len(m) > 1 {
						wpUser.GravatarHash = m[1]
					}
				}
				users = append(users, wpUser)
			}
		}
		resp2.Body.Close()
	} else if resp2 != nil {
		resp2.Body.Close()
	}

	// Detect version from readme.html
	req3, _ := http.NewRequestWithContext(ctx, "GET", baseURL+"/readme.html", nil)
	req3.Header.Set("User-Agent", UserAgent)
	resp3, err := client.Do(req3)
	if err == nil && resp3.StatusCode == 200 {
		body, _ := io.ReadAll(io.LimitReader(resp3.Body, 10*1024))
		resp3.Body.Close()
		vRe := regexp.MustCompile(`(?i)Version\s+(\d+\.\d+(?:\.\d+)?)`)
		if m := vRe.FindStringSubmatch(string(body)); len(m) > 1 {
			version = m[1]
		}
	} else if resp3 != nil {
		resp3.Body.Close()
	}

	// Detect theme from page source
	req4, _ := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
	req4.Header.Set("User-Agent", UserAgent)
	resp4, err := client.Do(req4)
	if err == nil && resp4.StatusCode == 200 {
		body, _ := io.ReadAll(io.LimitReader(resp4.Body, 500*1024))
		resp4.Body.Close()
		themeRe := regexp.MustCompile(`/wp-content/themes/([a-zA-Z0-9_-]+)/`)
		if m := themeRe.FindStringSubmatch(string(body)); len(m) > 1 {
			theme = m[1]
		}
		// Detect plugins from source
		bodyLower := strings.ToLower(string(body))
		srcPlugins := map[string][]string{
			"Elementor": {"elementor"}, "Divi": {"et_pb_", "divi"},
			"WP Rocket": {"wp-rocket"}, "Wordfence": {"wordfence"},
			"WooCommerce": {"woocommerce"}, "Yoast SEO": {"yoast", "wpseo"},
			"Contact Form 7": {"wpcf7", "contact-form-7"},
		}
		for name, patterns := range srcPlugins {
			for _, p := range patterns {
				if strings.Contains(bodyLower, p) {
					plugins = appendOdintUnique(plugins, name)
					break
				}
			}
		}
	} else if resp4 != nil {
		resp4.Body.Close()
	}

	return
}

func (tui *TUI) odintStoreWordPress(domain string, isWP bool, version, theme string, users []OdintWPUser, plugins []string, restData *OdintWPRESTData) {
	wpFlag := 0
	if isWP {
		wpFlag = 1
	}
	pluginsStr := strings.Join(plugins, ",")
	restJSON := ""
	if restData != nil {
		data, _ := json.Marshal(restData)
		restJSON = string(data)
	}
	tui.db.conn.Exec(`INSERT OR REPLACE INTO odint_wordpress_sites
		(domain, is_wordpress, wp_version, wp_theme, wp_plugins, rest_data, discovered)
		VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
		domain, wpFlag, version, theme, pluginsStr, restJSON)

	for _, u := range users {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_wordpress_users
			(domain, user_id, username, display_name, gravatar_hash, url, discovered)
			VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, u.ID, u.Username, u.DisplayName, u.GravatarHash, u.URL)
	}
}

// ===========================================================================
// PHASE 3: SUBDOMAIN ENUMERATION
// ===========================================================================

func odintEnumerateSubdomains(ctx context.Context, client *http.Client, domain string, cfg OdintConfig) []OdintSubdomainSource {
	seen := &sync.Map{}
	var mu sync.Mutex
	var results []OdintSubdomainSource
	var wg sync.WaitGroup

	// Source 1: crt.sh
	wg.Add(1)
	go func() {
		defer wg.Done()
		apiURL := fmt.Sprintf("https://crt.sh/?q=%%.%s&output=json", url.QueryEscape(domain))
		reqCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
		defer cancel()
		req, err := http.NewRequestWithContext(reqCtx, "GET", apiURL, nil)
		if err != nil {
			return
		}
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil || resp.StatusCode != 200 {
			if resp != nil {
				resp.Body.Close()
			}
			return
		}
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 5*1024*1024))
		resp.Body.Close()
		var crtResults []struct {
			CommonName string `json:"common_name"`
			NameValue  string `json:"name_value"`
		}
		if json.Unmarshal(body, &crtResults) != nil {
			return
		}
		for _, r := range crtResults {
			for _, name := range strings.Split(r.NameValue, "\n") {
				name = strings.TrimSpace(strings.TrimPrefix(strings.ToLower(name), "*."))
				if name == "" || !strings.HasSuffix(name, domain) {
					continue
				}
				if _, loaded := seen.LoadOrStore(name, true); loaded {
					continue
				}
				ip := odintResolveDNS(name)
				mu.Lock()
				results = append(results, OdintSubdomainSource{Subdomain: name, Source: "crtsh", IP: ip})
				mu.Unlock()
			}
		}
	}()

	// Source 2: SecurityTrails (if configured)
	if cfg.HasSecurityTrailsKey() {
		wg.Add(1)
		go func() {
			defer wg.Done()
			apiURL := fmt.Sprintf("https://api.securitytrails.com/v1/domain/%s/subdomains", url.PathEscape(domain))
			reqCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
			defer cancel()
			req, err := http.NewRequestWithContext(reqCtx, "GET", apiURL, nil)
			if err != nil {
				return
			}
			req.Header.Set("APIKEY", cfg.SecurityTrailsKey)
			req.Header.Set("Accept", "application/json")
			resp, err := client.Do(req)
			if err != nil || resp.StatusCode != 200 {
				if resp != nil {
					resp.Body.Close()
				}
				return
			}
			body, _ := io.ReadAll(io.LimitReader(resp.Body, 2*1024*1024))
			resp.Body.Close()
			var stResult struct {
				Subdomains []string `json:"subdomains"`
			}
			if json.Unmarshal(body, &stResult) != nil {
				return
			}
			for _, sub := range stResult.Subdomains {
				fqdn := strings.ToLower(sub + "." + domain)
				if _, loaded := seen.LoadOrStore(fqdn, true); loaded {
					continue
				}
				ip := odintResolveDNS(fqdn)
				mu.Lock()
				results = append(results, OdintSubdomainSource{Subdomain: fqdn, Source: "securitytrails", IP: ip})
				mu.Unlock()
			}
		}()
	}

	// Source 3: Pattern-based
	wg.Add(1)
	go func() {
		defer wg.Done()
		prefixes := []string{
			"dev", "staging", "api", "mail", "admin", "cdn", "internal", "test",
			"beta", "demo", "app", "portal", "blog", "shop", "store", "docs",
			"status", "vpn", "remote", "git", "ci", "jenkins", "grafana",
			"monitoring", "elastic", "kibana", "k8s", "registry", "vault", "console",
		}
		sem := make(chan struct{}, 10)
		var pwg sync.WaitGroup
		for _, prefix := range prefixes {
			if ctx.Err() != nil {
				break
			}
			fqdn := strings.ToLower(prefix + "." + domain)
			if _, loaded := seen.LoadOrStore(fqdn, true); loaded {
				continue
			}
			pwg.Add(1)
			sem <- struct{}{}
			go func(fqdn string) {
				defer pwg.Done()
				defer func() { <-sem }()
				ip := odintResolveDNS(fqdn)
				if ip == "" {
					return
				}
				mu.Lock()
				results = append(results, OdintSubdomainSource{Subdomain: fqdn, Source: "pattern", IP: ip})
				mu.Unlock()
			}(fqdn)
		}
		pwg.Wait()
	}()

	wg.Wait()
	return results
}

func odintResolveDNS(hostname string) string {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	addrs, err := net.DefaultResolver.LookupHost(ctx, hostname)
	if err != nil || len(addrs) == 0 {
		return ""
	}
	return addrs[0]
}

func (tui *TUI) odintStoreSubdomains(domain string, subs []OdintSubdomainSource) {
	for _, s := range subs {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_subdomains
			(domain, subdomain, source, ip, status_code, discovered)
			VALUES (?, ?, ?, ?, 0, CURRENT_TIMESTAMP)`,
			domain, s.Subdomain, s.Source, s.IP)
	}
}

// ===========================================================================
// PHASE 3: EXPOSED PATH DETECTION
// ===========================================================================

func odintCheckExposedPaths(ctx context.Context, client *http.Client, baseURL string) []OdintExposedPath {
	sensitivePaths := map[string]string{
		// ── Git / Version Control ──────────────────────────────────────
		"/.git/config":  "git",
		"/.git/HEAD":    "git",
		"/.svn/entries": "svn",
		"/.svn/":        "svn",
		"/.hg/":         "svn",
		"/.gitignore":   "config",

		// ── Environment Files ─────────────────────────────────────────
		"/.env":            "env",
		"/.env.local":      "env",
		"/.env.production":  "env",
		"/.env.development": "env",
		"/.env.staging":     "env",
		"/.env.backup":      "env",
		"/.dockerenv":       "env",

		// ── Standard Files ────────────────────────────────────────────
		"/humans.txt":        "misc",
		"/ads.txt":           "misc",
		"/app-ads.txt":       "misc",
		"/browserconfig.xml": "misc",
		"/LICENSE":           "misc",
		"/README":            "misc",
		"/README.md":         "misc",
		"/CHANGELOG":         "misc",
		"/CHANGELOG.md":      "misc",
		"/VERSION":           "misc",
		"/manifest.json":     "misc",
		"/site.webmanifest":  "misc",
		"/crossdomain.xml":   "misc",
		"/.DS_Store":         "misc",

		// ── Admin / Login Panels ──────────────────────────────────────
		"/admin/":                    "admin",
		"/administrator/":            "admin",
		"/wp-admin/":                 "admin",
		"/phpmyadmin/":               "admin",
		"/adminer.php":               "admin",
		"/signin/":                   "admin",
		"/user/login":                "admin",
		"/panel/":                    "admin",
		"/dashboard/":                "admin",
		"/cpanel/":                   "admin",
		"/webmail/":                  "admin",
		"/portal/":                   "admin",
		"/console/":                  "admin",
		"/manager/":                  "admin",
		"/login.php":                 "admin",
		"/administrator/index.php":   "admin",

		// ── Config / Sensitive Files ──────────────────────────────────
		"/.htaccess":           "config",
		"/.htpasswd":           "credential",
		"/web.config":          "config",
		"/config.php.bak":      "backup",
		"/wp-config.php.bak":   "backup",
		"/wp-config.php~":      "backup",
		"/config.yml":          "config",
		"/application.yml":     "config",
		"/appsettings.json":    "config",
		"/config/database.yml": "config",
		"/Dockerfile":          "config",
		"/docker-compose.yml":  "config",
		"/Makefile":            "config",
		"/Gruntfile.js":        "config",
		"/Gulpfile.js":         "config",
		"/webpack.config.js":   "config",
		"/Vagrantfile":         "config",
		"/.editorconfig":       "config",
		"/.babelrc":            "config",
		"/tsconfig.json":       "config",
		"/.npmrc":              "config",

		// ── Credential Files ──────────────────────────────────────────
		"/.aws/credentials":     "credential",
		"/.ssh/id_rsa":          "credential",
		"/.ssh/authorized_keys": "credential",
		"/id_rsa":               "credential",

		// ── CI / CD ───────────────────────────────────────────────────
		"/.travis.yml":    "ci",
		"/.gitlab-ci.yml": "ci",
		"/Jenkinsfile":    "ci",

		// ── Backup / Archive ──────────────────────────────────────────
		"/backup.sql":    "backup",
		"/dump.sql":      "backup",
		"/database.sql":  "backup",
		"/backup/":       "backup",
		"/backups/":      "backup",
		"/bak/":          "backup",
		"/old/":          "backup",
		"/archive/":      "backup",
		"/temp/":         "backup",
		"/dump/":         "backup",
		"/sql/":          "backup",
		"/export/":       "backup",
		"/private/":      "backup",
		"/backup.tar.gz": "backup",
		"/backup.zip":    "backup",
		"/site.tar.gz":   "backup",
		"/db.sql":        "backup",
		"/data.sql":      "backup",

		// ── Log / Debug ───────────────────────────────────────────────
		"/debug.log":             "log",
		"/error.log":             "log",
		"/access.log":            "log",
		"/wp-content/debug.log":  "log",
		"/logs/":                 "log",
		"/application.log":       "log",
		"/server-status":         "debug",
		"/server-info":           "debug",
		"/elmah.axd":             "debug",
		"/trace.axd":             "debug",
		"/error_log":             "log",
		"/debug/":                "debug",
		"/var/log/":              "log",
		"/phpinfo.php":           "debug",
		"/info.php":              "debug",
		"/test.php":              "debug",

		// ── CMS-Specific (WordPress) ──────────────────────────────────
		"/xmlrpc.php":   "cms",
		"/wp-cron.php":  "cms",
		"/readme.html":  "cms",
		"/wp-login.php": "cms",

		// ── CMS-Specific (Drupal) ─────────────────────────────────────
		"/CHANGELOG.txt":      "cms",
		"/update.php":         "cms",
		"/install.php":        "cms",
		"/INSTALL.txt":        "cms",
		"/core/CHANGELOG.txt": "cms",

		// ── Source / Package Manager Files ─────────────────────────────
		"/.idea/":          "source",
		"/.vscode/":        "source",
		"/composer.json":   "config",
		"/package.json":    "config",
		"/yarn.lock":       "source",
		"/Gemfile":         "source",
		"/Gemfile.lock":    "source",
		"/requirements.txt": "source",
		"/Pipfile":          "source",
		"/pom.xml":          "source",
		"/build.gradle":     "source",

		// ── WordPress REST API ─────────────────────────────────────────
		"/wp-json/wp/v2/posts":              "api",
		"/wp-json/wp/v2/pages":              "api",
		"/wp-json/wp/v2/media":              "api",
		"/wp-json/wp/v2/categories":         "api",
		"/wp-json/wp/v2/tags":               "api",
		"/wp-json/wp/v2/comments":           "api",
		"/wp-json/wp/v2/types":              "api",
		"/wp-json/wp/v2/taxonomies":         "api",
		"/wp-json/wp/v2/settings":           "api",
		"/wp-json/wc/v3/products":           "api",
		"/wp-json/yoast/v1/get_head":        "api",
		"/wp-json/acf/v3/posts":             "api",
		"/wp-json/wp-site-health/v1/tests":  "api",
		"/wp-json/oembed/1.0/embed":         "api",
		"/wp-json/wp/v2/search":             "api",
		"/wp-json/wp/v2/themes":             "api",
		"/wp-json/wp/v2/plugins":            "api",

		// ── GraphQL ────────────────────────────────────────────────────
		"/graphql":      "api",
		"/graphiql":     "api",
		"/v1/graphql":   "api",
		"/api/graphql":  "api",
		"/query":        "api",

		// ── REST API Endpoints ─────────────────────────────────────────
		"/api/v1/":      "api",
		"/api/v2/":      "api",
		"/api/v3/":      "api",
		"/api/users":    "api",
		"/api/admin":    "api",
		"/api/auth":     "api",
		"/api/config":   "api",
		"/api/health":   "api",
		"/api/version":  "api",
		"/rest/":        "api",
		"/_api/":        "api",
		"/api/swagger":  "api",
		"/api/docs":     "api",
		"/api/me":       "api",
		"/api/status":   "api",
		"/api/info":     "api",
		"/api/debug":    "api",
		"/api/test":     "api",
		"/v1/api/":      "api",
		"/v2/api/":      "api",

		// ── Auth Endpoints ─────────────────────────────────────────────
		"/oauth/":                             "auth",
		"/oauth/token":                        "auth",
		"/oauth/authorize":                    "auth",
		"/auth/":                              "auth",
		"/sso/":                               "auth",
		"/cas/":                               "auth",
		"/.well-known/openid-configuration":   "auth",
		"/auth/login":                         "auth",

		// ── API Documentation ──────────────────────────────────────────
		"/swagger.json":  "api",
		"/openapi.json":  "api",
		"/swagger-ui/":   "api",
		"/redoc":         "api",
		"/rapidoc":       "api",
		"/swagger.yaml":  "api",
		"/api-docs/":     "api",
		"/swagger/":      "api",
		"/api-docs":      "api",

		// ── Debug / Health Checks ──────────────────────────────────────
		"/health":      "debug",
		"/status":      "debug",
		"/metrics":     "debug",
		"/prometheus":  "debug",
		"/healthz":     "debug",
		"/ready":       "debug",
		"/readyz":      "debug",
		"/livez":       "debug",
		"/ping":        "debug",
		"/_health":     "debug",

		// ── SOAP / WSDL ────────────────────────────────────────────────
		"/ws/":           "api",
		"/wsdl/":         "api",
		"/soap/":         "api",
		"/Service.asmx":  "api",
		"/services/":     "api",

		// ── Cloud / Static Storage ─────────────────────────────────────
		"/s3/":       "cloud",
		"/storage/":  "cloud",
		"/blob/":     "cloud",
		"/cdn/":      "cloud",
		"/assets/":   "cloud",
		"/static/":   "cloud",
		"/media/":    "cloud",

		// ── CI/CD Config ──────────────────────────────────────────────
		"/.github/workflows/": "ci",
		"/.circleci/config.yml": "ci",

		// ── Infrastructure as Code ────────────────────────────────────
		"/.terraform/":       "iac",
		"/terraform.tfstate": "iac",
		"/ansible.cfg":       "iac",
		"/Chart.yaml":        "iac",
		"/playbook.yml":      "iac",
		"/values.yaml":       "iac",

		// ── Management UIs ────────────────────────────────────────────
		"/airflow/":   "admin",
		"/sidekiq/":   "admin",
		"/hangfire/":  "admin",
		"/flower/":    "admin",
		"/jupyter/":   "admin",
		"/mlflow/":    "admin",
		"/zeppelin/":  "admin",
		"/superset/":  "admin",
		"/redash/":    "admin",
		"/metabase/":  "admin",

		// ── GIS Services ──────────────────────────────────────────────
		"/geoserver/":             "api",
		"/arcgis/rest/services/":  "api",

		// ── Drupal JSONAPI ────────────────────────────────────────────
		"/jsonapi":                "api",
		"/jsonapi/node/article":   "api",
		"/jsonapi/user/user":      "api",

		// ── Mobile API Endpoints ──────────────────────────────────────
		"/mobile/":   "api",
		"/app/":      "api",
		"/ios/":      "api",
		"/android/":  "api",

		// ── Joomla-Specific ───────────────────────────────────────────
		"/administrator/manifests/files/joomla.xml": "cms",
		"/language/en-GB/en-GB.xml":                 "cms",
		"/plugins/system/":                          "cms",

		// ── Virtualization / CI UIs ───────────────────────────────────
		"/pve/":       "admin",
		"/ui/":        "admin",
		"/teamcity/":  "admin",
		"/bamboo/":    "admin",

		// ── Well-Known / Email Security ───────────────────────────────
		"/.well-known/mta-sts.txt":           "misc",
		"/.well-known/autoconfig/mail/":      "misc",
	}

	var results []OdintExposedPath
	var mu sync.Mutex
	sem := make(chan struct{}, 5)
	var wg sync.WaitGroup

	for path, pathType := range sensitivePaths {
		if ctx.Err() != nil {
			break
		}
		wg.Add(1)
		sem <- struct{}{}
		go func(path, pathType string) {
			defer wg.Done()
			defer func() { <-sem }()
			req, err := http.NewRequestWithContext(ctx, "HEAD", baseURL+path, nil)
			if err != nil {
				return
			}
			req.Header.Set("User-Agent", UserAgent)
			resp, err := client.Do(req)
			if err != nil {
				return
			}
			resp.Body.Close()
			if resp.StatusCode == 200 || resp.StatusCode == 403 {
				mu.Lock()
				results = append(results, OdintExposedPath{Path: path, StatusCode: resp.StatusCode, Type: pathType})
				mu.Unlock()
			}
		}(path, pathType)
	}
	wg.Wait()
	return results
}

func (tui *TUI) odintStoreExposedPaths(domain string, paths []OdintExposedPath) {
	for _, p := range paths {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_exposed_paths
			(domain, path, status_code, path_type, discovered) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, p.Path, p.StatusCode, p.Type)
	}
}

// ===========================================================================
// PHASE 3: SECRETS DETECTION
// ===========================================================================

func odintDeepSecretScan(baseURL, body string) []OdintSecretFinding {
	var results []OdintSecretFinding
	seen := make(map[string]bool)

	patterns := map[string]struct {
		re       *regexp.Regexp
		severity string
	}{
		// ── AWS ──────────────────────────────────────────────────────────
		"AWS Access Key":    {regexp.MustCompile(`AKIA[0-9A-Z]{16}`), "critical"},
		"AWS Secret Key":    {regexp.MustCompile(`['"](?:aws_secret|AWS_SECRET|aws_secret_access_key|AWS_SECRET_ACCESS_KEY)['"]\s*[:=]\s*['"]([A-Za-z0-9/+=]{40})['"]`), "critical"},
		// ── Azure ────────────────────────────────────────────────────────
		"Azure Conn String": {regexp.MustCompile(`(?i)DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{44,}`), "critical"},
		"Azure SAS Token":   {regexp.MustCompile(`(?i)[?&]sig=[A-Za-z0-9%+/=]{40,}`), "high"},
		// ── Google Cloud ─────────────────────────────────────────────────
		"Google API Key":        {regexp.MustCompile(`AIza[0-9A-Za-z_-]{35}`), "high"},
		"Google Service Account": {regexp.MustCompile(`"type"\s*:\s*"service_account"`), "critical"},
		"Google OAuth Token":    {regexp.MustCompile(`ya29\.[0-9A-Za-z_-]+`), "high"},
		// ── Stripe ───────────────────────────────────────────────────────
		"Stripe Secret Key":      {regexp.MustCompile(`sk_live_[0-9a-zA-Z]{24,}`), "critical"},
		"Stripe Publishable Key": {regexp.MustCompile(`pk_live_[0-9a-zA-Z]{24,}`), "medium"},
		"Stripe Test Secret":     {regexp.MustCompile(`sk_test_[0-9a-zA-Z]{24,}`), "high"},
		"Stripe Test Publishable": {regexp.MustCompile(`pk_test_[0-9a-zA-Z]{24,}`), "low"},
		// ── Private Keys ─────────────────────────────────────────────────
		"SSH Private Key": {regexp.MustCompile(`-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----`), "critical"},
		"PGP Private Key": {regexp.MustCompile(`-----BEGIN PGP PRIVATE KEY BLOCK-----`), "critical"},
		// ── Database Connection Strings ──────────────────────────────────
		"PostgreSQL Conn": {regexp.MustCompile(`postgresql://[^\s'"<>]+`), "critical"},
		"MongoDB Conn":    {regexp.MustCompile(`mongodb(?:\+srv)?://[^\s'"<>]+`), "critical"},
		"MySQL Conn":      {regexp.MustCompile(`mysql://[^\s'"<>]+`), "critical"},
		"Redis Conn":      {regexp.MustCompile(`redis://[^\s'"<>]+`), "critical"},
		// ── GitHub Tokens ────────────────────────────────────────────────
		"GitHub OAuth":  {regexp.MustCompile(`gho_[A-Za-z0-9_]{36,}`), "high"},
		"GitHub PAT":    {regexp.MustCompile(`ghp_[A-Za-z0-9_]{36,}`), "high"},
		"GitHub App":    {regexp.MustCompile(`ghs_[A-Za-z0-9_]{36,}`), "high"},
		// ── Slack ────────────────────────────────────────────────────────
		"Slack Token":   {regexp.MustCompile(`xox[bpras]-[0-9a-zA-Z-]+`), "high"},
		"Slack Webhook": {regexp.MustCompile(`https://hooks\.slack\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+`), "high"},
		// ── Discord ──────────────────────────────────────────────────────
		"Discord Webhook":   {regexp.MustCompile(`https://discord(?:app)?\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+`), "high"},
		"Discord Bot Token": {regexp.MustCompile(`[MN][A-Za-z\d]{23,28}\.[\w-]{6}\.[\w-]{27,}`), "critical"},
		// ── Other Services ───────────────────────────────────────────────
		"SendGrid Key":    {regexp.MustCompile(`SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}`), "high"},
		"Twilio Key":      {regexp.MustCompile(`SK[0-9a-fA-F]{32}`), "high"},
		"Mailgun Key":     {regexp.MustCompile(`key-[0-9a-zA-Z]{32}`), "high"},
		"Heroku API Key":  {regexp.MustCompile(`(?i:heroku[_-]?api[_-]?key)\s*[:=]\s*['"]?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}['"]?`), "high"},
		"Square OAuth":    {regexp.MustCompile(`sq0csp-[0-9A-Za-z_-]{43}`), "high"},
		"NPM Token":       {regexp.MustCompile(`(?i)npm_[A-Za-z0-9]{36}`), "critical"},
		"PyPI Token":      {regexp.MustCompile(`pypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}`), "critical"},
		"Shopify API Key": {regexp.MustCompile(`shpat_[a-fA-F0-9]{32}`), "high"},
		// ── Generic Patterns ─────────────────────────────────────────────
		"Generic API Key": {regexp.MustCompile(`['"](?i:api[_-]?key|apikey|api[_-]?secret)['"]\s*[:=]\s*['"]([A-Za-z0-9_-]{20,})['"]`), "medium"},
		"Firebase URL":    {regexp.MustCompile(`[a-z0-9-]+\.firebaseio\.com`), "medium"},
		"JWT Token":       {regexp.MustCompile(`eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+`), "medium"},
	}

	for name, pat := range patterns {
		for _, match := range pat.re.FindAllString(body, 3) {
			truncated := match
			if len(truncated) > 50 {
				truncated = truncated[:47] + "..."
			}
			key := name + ":" + truncated
			if seen[key] {
				continue
			}
			seen[key] = true
			results = append(results, OdintSecretFinding{
				Type:     name,
				Value:    truncated,
				Location: baseURL,
				Severity: pat.severity,
			})
		}
	}
	return results
}

func (tui *TUI) odintStoreSecrets(domain string, secrets []OdintSecretFinding) {
	for _, s := range secrets {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_secrets
			(domain, secret_type, value, location, severity, context, discovered)
			VALUES (?, ?, ?, ?, ?, '', CURRENT_TIMESTAMP)`,
			domain, s.Type, s.Value, s.Location, s.Severity)
	}
}

// ===========================================================================
// PHASE 4: JWT TOKEN ANALYSIS
// ===========================================================================

func odintAnalyzeJWTs(baseURL, body string, headers map[string]string) []OdintJWTToken {
	var tokens []OdintJWTToken
	jwtRe := regexp.MustCompile(`eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+`)
	seen := make(map[string]bool)

	allText := body
	for _, v := range headers {
		allText += " " + v
	}

	for _, match := range jwtRe.FindAllString(allText, 20) {
		if seen[match] {
			continue
		}
		seen[match] = true

		token := OdintJWTToken{Raw: match}
		parts := strings.SplitN(match, ".", 3)
		if len(parts) == 3 {
			if headerJSON, err := odintBase64URLDecode(parts[0]); err == nil {
				var hdr map[string]interface{}
				if json.Unmarshal(headerJSON, &hdr) == nil {
					token.Header = hdr
					if alg, ok := hdr["alg"]; ok {
						token.Algorithm = fmt.Sprintf("%v", alg)
					}
				}
			}
			if payloadJSON, err := odintBase64URLDecode(parts[1]); err == nil {
				var payload map[string]interface{}
				if json.Unmarshal(payloadJSON, &payload) == nil {
					token.Payload = payload
					token.Claims = payload
					if iss, ok := payload["iss"]; ok {
						token.Issuer = fmt.Sprintf("%v", iss)
					}
					if sub, ok := payload["sub"]; ok {
						token.Subject = fmt.Sprintf("%v", sub)
					}
				}
			}
		}

		// Guess location
		if strings.Contains(body, match) {
			if strings.Contains(body, "<script") {
				token.Location = "javascript"
			} else {
				token.Location = "html"
			}
		} else {
			token.Location = "header"
		}

		tokens = append(tokens, token)
	}
	return tokens
}

func odintBase64URLDecode(s string) ([]byte, error) {
	// Add padding
	switch len(s) % 4 {
	case 2:
		s += "=="
	case 3:
		s += "="
	}
	return base64.URLEncoding.DecodeString(s)
}

func (tui *TUI) odintStoreJWTs(domain string, tokens []OdintJWTToken) {
	for _, t := range tokens {
		hash := fmt.Sprintf("%x", sha256.Sum256([]byte(t.Raw)))
		headerJSON, _ := json.Marshal(t.Header)
		payloadJSON, _ := json.Marshal(t.Payload)
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_jwt_tokens
			(domain, token_hash, algorithm, issuer, subject, location, header_json, payload_json, discovered)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, hash[:16], t.Algorithm, t.Issuer, t.Subject, t.Location, string(headerJSON), string(payloadJSON))
	}
}

// ===========================================================================
// PHASE 4: GRAPHQL ENDPOINT DETECTION
// ===========================================================================

func odintDetectGraphQL(ctx context.Context, client *http.Client, baseURL string) []OdintGraphQLEndpoint {
	var endpoints []OdintGraphQLEndpoint
	paths := []string{"/graphql", "/graphiql", "/v1/graphql", "/api/graphql", "/query", "/gql"}

	for _, path := range paths {
		if ctx.Err() != nil {
			break
		}
		testURL := baseURL + path
		req, _ := http.NewRequestWithContext(ctx, "GET", testURL, nil)
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil {
			continue
		}
		resp.Body.Close()
		if resp.StatusCode != 200 && resp.StatusCode != 400 {
			continue
		}

		ep := OdintGraphQLEndpoint{URL: testURL}

		// Try introspection
		introspectionQuery := `{"query": "{ __schema { types { name } queryType { fields { name } } mutationType { fields { name } } subscriptionType { fields { name } } } }"}`
		req2, _ := http.NewRequestWithContext(ctx, "POST", testURL, strings.NewReader(introspectionQuery))
		req2.Header.Set("Content-Type", "application/json")
		req2.Header.Set("User-Agent", UserAgent)
		resp2, err := client.Do(req2)
		if err != nil {
			endpoints = append(endpoints, ep)
			continue
		}
		body, _ := io.ReadAll(io.LimitReader(resp2.Body, 500*1024))
		resp2.Body.Close()

		var gqlResp struct {
			Data struct {
				Schema struct {
					Types []struct {
						Name string `json:"name"`
					} `json:"types"`
					QueryType *struct {
						Fields []struct {
							Name string `json:"name"`
						} `json:"fields"`
					} `json:"queryType"`
					MutationType *struct {
						Fields []struct {
							Name string `json:"name"`
						} `json:"fields"`
					} `json:"mutationType"`
					SubscriptionType *struct {
						Fields []struct {
							Name string `json:"name"`
						} `json:"fields"`
					} `json:"subscriptionType"`
				} `json:"__schema"`
			} `json:"data"`
		}

		if json.Unmarshal(body, &gqlResp) == nil && len(gqlResp.Data.Schema.Types) > 0 {
			ep.Introspection = true
			for _, t := range gqlResp.Data.Schema.Types {
				if !strings.HasPrefix(t.Name, "__") {
					ep.Types = append(ep.Types, t.Name)
				}
			}
			if gqlResp.Data.Schema.QueryType != nil {
				for _, f := range gqlResp.Data.Schema.QueryType.Fields {
					ep.Queries = append(ep.Queries, f.Name)
				}
			}
			if gqlResp.Data.Schema.MutationType != nil {
				for _, f := range gqlResp.Data.Schema.MutationType.Fields {
					ep.Mutations = append(ep.Mutations, f.Name)
				}
			}
			if gqlResp.Data.Schema.SubscriptionType != nil {
				for _, f := range gqlResp.Data.Schema.SubscriptionType.Fields {
					ep.Subscriptions = append(ep.Subscriptions, f.Name)
				}
			}
		}

		endpoints = append(endpoints, ep)
	}
	return endpoints
}

func (tui *TUI) odintStoreGraphQL(domain string, endpoints []OdintGraphQLEndpoint) {
	for _, ep := range endpoints {
		introFlag := 0
		if ep.Introspection {
			introFlag = 1
		}
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_graphql_endpoints
			(domain, url, introspection, types, queries, mutations, subscriptions, discovered)
			VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, ep.URL, introFlag,
			strings.Join(ep.Types, ","), strings.Join(ep.Queries, ","),
			strings.Join(ep.Mutations, ","), strings.Join(ep.Subscriptions, ","))
	}
}

// ===========================================================================
// PHASE 4: JAVASCRIPT ANALYSIS
// ===========================================================================

func odintAnalyzeJavaScript(body string) (globals, configs, envLeaks, debugFlags, featureFlags []string) {
	// JS Globals
	globalPatterns := map[string]string{
		"__NEXT_DATA__": "Next.js", "__NUXT__": "Nuxt.js", "__VUE__": "Vue.js",
		"__REACT_DEVTOOLS": "React DevTools", "__REDUX_DEVTOOLS": "Redux DevTools",
		"__APOLLO_STATE__": "Apollo Client", "__GATSBY": "Gatsby",
		"__SENTRY__": "Sentry", "window.dataLayer": "Google Tag Manager",
	}
	for pattern, name := range globalPatterns {
		if strings.Contains(body, pattern) {
			globals = append(globals, name+" ("+pattern+")")
		}
	}

	// Config objects
	configPatterns := []string{
		"window.config", "window.env", "window.APP_CONFIG", "window.__CONFIG__",
		"window.__ENV__", "window.SETTINGS", "window.appConfig", "window.serverData",
		"window.__INITIAL_STATE__", "window.__PRELOADED_STATE__",
	}
	for _, pattern := range configPatterns {
		if strings.Contains(body, pattern) {
			configs = append(configs, pattern)
		}
	}

	// Environment leaks
	envPatterns := []string{
		"process.env", "REACT_APP_", "NEXT_PUBLIC_", "VUE_APP_", "VITE_", "GATSBY_", "NUXT_PUBLIC_",
	}
	for _, pattern := range envPatterns {
		if strings.Contains(body, pattern) {
			envLeaks = append(envLeaks, pattern)
		}
	}

	// Debug flags
	debugPatterns := []string{
		"debug:true", "DEBUG=true", "devMode:true", "isDebug:true",
		"__DEV__:true", "isDev:true", "NODE_ENV=development",
	}
	bodyLower := strings.ToLower(body)
	for _, pattern := range debugPatterns {
		if strings.Contains(bodyLower, strings.ToLower(pattern)) {
			debugFlags = append(debugFlags, pattern)
		}
	}

	// Feature flags
	flagPatterns := []string{
		"LaunchDarkly", "split.io", "optimizely", "unleash",
		"flagsmith", "growthbook", "statsig", "configcat",
		"featureFlags", "feature_toggles",
	}
	for _, pattern := range flagPatterns {
		if strings.Contains(bodyLower, strings.ToLower(pattern)) {
			featureFlags = append(featureFlags, pattern)
		}
	}

	return
}

// ===========================================================================
// PHASE 4: CSP ANALYSIS
// ===========================================================================

func odintAnalyzeCSP(headers map[string]string) []OdintCSPDirective {
	var directives []OdintCSPDirective
	cspHeader := ""
	for k, v := range headers {
		if strings.ToLower(k) == "content-security-policy" || strings.ToLower(k) == "content-security-policy-report-only" {
			cspHeader = v
			break
		}
	}
	if cspHeader == "" {
		return nil
	}

	parts := strings.Split(cspHeader, ";")
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}
		tokens := strings.Fields(part)
		if len(tokens) == 0 {
			continue
		}
		directives = append(directives, OdintCSPDirective{
			Directive: tokens[0],
			Values:    tokens[1:],
		})
	}
	return directives
}

func (tui *TUI) odintStoreCSP(domain string, directives []OdintCSPDirective) {
	for _, d := range directives {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_csp_directives
			(domain, directive, "values", discovered)
			VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, d.Directive, strings.Join(d.Values, " "))
	}
}

// ===========================================================================
// PHASE 4: PAGE INTELLIGENCE
// ===========================================================================

func odintExtractIframeSources(body string) []OdintIframeSource {
	var iframes []OdintIframeSource
	re := regexp.MustCompile(`(?i)<(?:iframe|embed|object|applet)[^>]+(?:src|data)=["']([^"']+)["']`)
	sandboxRe := regexp.MustCompile(`(?i)sandbox=["']([^"']*)["']`)
	for _, match := range re.FindAllStringSubmatch(body, 50) {
		if len(match) > 1 {
			iframe := OdintIframeSource{URL: match[1]}
			if sm := sandboxRe.FindStringSubmatch(match[0]); len(sm) > 1 {
				iframe.Sandbox = sm[1]
			}
			if strings.Contains(strings.ToLower(match[0]), "iframe") {
				iframe.ObjectType = "iframe"
			} else if strings.Contains(strings.ToLower(match[0]), "embed") {
				iframe.ObjectType = "embed"
			} else {
				iframe.ObjectType = "object"
			}
			iframes = append(iframes, iframe)
		}
	}
	return iframes
}

func (tui *TUI) odintStoreIframes(domain string, iframes []OdintIframeSource) {
	for _, iframe := range iframes {
		nested := 0
		if iframe.IsNested {
			nested = 1
		}
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_iframe_sources
			(domain, url, sandbox, object_type, is_nested, discovered)
			VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, iframe.URL, iframe.Sandbox, iframe.ObjectType, nested)
	}
}

func odintDetectWebhookURLs(body string) []OdintWebhookURL {
	var webhooks []OdintWebhookURL
	patterns := map[string]*regexp.Regexp{
		"Slack":   regexp.MustCompile(`https://hooks\.slack\.com/services/[A-Za-z0-9/]+`),
		"Discord": regexp.MustCompile(`https://discord(?:app)?\.com/api/webhooks/\d+/[A-Za-z0-9_-]+`),
		"Teams":   regexp.MustCompile(`https://[a-z0-9]+\.webhook\.office\.com/webhookb2/[^\s"']+`),
		"Zapier":  regexp.MustCompile(`https://hooks\.zapier\.com/hooks/catch/\d+/[a-z0-9]+`),
	}
	for platform, re := range patterns {
		for _, match := range re.FindAllString(body, 5) {
			webhooks = append(webhooks, OdintWebhookURL{Platform: platform, URL: match, Location: "body"})
		}
	}
	return webhooks
}

func odintExtractReportingEndpoints(headers map[string]string) []OdintReportingEndpoint {
	var endpoints []OdintReportingEndpoint
	for k, v := range headers {
		kLower := strings.ToLower(k)
		switch {
		case kLower == "report-to":
			endpoints = append(endpoints, OdintReportingEndpoint{Type: "Report-To", Config: v})
		case kLower == "nel":
			endpoints = append(endpoints, OdintReportingEndpoint{Type: "NEL", Config: v})
		case kLower == "expect-ct":
			endpoints = append(endpoints, OdintReportingEndpoint{Type: "Expect-CT", Config: v})
		}
	}
	// CSP report-uri
	if csp, ok := headers["Content-Security-Policy"]; ok {
		if idx := strings.Index(csp, "report-uri"); idx >= 0 {
			rest := csp[idx+10:]
			if semiIdx := strings.Index(rest, ";"); semiIdx >= 0 {
				rest = rest[:semiIdx]
			}
			endpoints = append(endpoints, OdintReportingEndpoint{Type: "CSP report-uri", URL: strings.TrimSpace(rest)})
		}
	}
	return endpoints
}

func (tui *TUI) odintStoreReportingEndpoints(domain string, endpoints []OdintReportingEndpoint) {
	for _, ep := range endpoints {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_reporting_endpoints
			(domain, endpoint_type, url, config, discovered)
			VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, ep.Type, ep.URL, ep.Config)
	}
}

// ===========================================================================
// PHASE 4: EMAIL SECURITY DNS
// ===========================================================================

func odintCheckEmailSecurityDNS(ctx context.Context, domain string) *OdintEmailSecurityDNS {
	result := &OdintEmailSecurityDNS{}
	resolver := &net.Resolver{}

	// MTA-STS
	mtaCtx, mtaCancel := context.WithTimeout(ctx, 5*time.Second)
	txts, _ := resolver.LookupTXT(mtaCtx, "_mta-sts."+domain)
	mtaCancel()
	for _, txt := range txts {
		if strings.HasPrefix(txt, "v=STSv1") {
			result.MTASTSPolicy = txt
			break
		}
	}

	// BIMI
	bimiCtx, bimiCancel := context.WithTimeout(ctx, 5*time.Second)
	txts, _ = resolver.LookupTXT(bimiCtx, "default._bimi."+domain)
	bimiCancel()
	for _, txt := range txts {
		if strings.Contains(txt, "v=BIMI1") {
			result.BIMIRecord = txt
			break
		}
	}

	// SMTP TLS Reporting
	tlsCtx, tlsCancel := context.WithTimeout(ctx, 5*time.Second)
	txts, _ = resolver.LookupTXT(tlsCtx, "_smtp._tls."+domain)
	tlsCancel()
	for _, txt := range txts {
		if strings.Contains(txt, "v=TLSRPTv1") {
			result.SMTPTLSRpt = txt
			break
		}
	}

	return result
}

func (tui *TUI) odintStoreEmailSecurity(domain string, es *OdintEmailSecurityDNS) {
	if es == nil {
		return
	}
	tui.db.conn.Exec(`INSERT OR REPLACE INTO odint_email_security
		(domain, mta_sts_policy, bimi_record, smtp_tls_rpt, dane_records, discovered)
		VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
		domain, es.MTASTSPolicy, es.BIMIRecord, es.SMTPTLSRpt, strings.Join(es.DANERecords, "|"))
}

// ===========================================================================
// PHASE 4: ERROR PAGE ANALYSIS
// ===========================================================================

func odintCheckErrorPages(ctx context.Context, client *http.Client, baseURL string) []OdintErrorPageResult {
	var results []OdintErrorPageResult
	errorPaths := []struct {
		path string
		code int
	}{
		{"/asdkjhqwekjhnotfound_" + strconv.FormatInt(time.Now().Unix(), 36), 404},
		{"/", 0}, // Will check other methods
	}

	for _, ep := range errorPaths {
		if ctx.Err() != nil {
			break
		}
		req, _ := http.NewRequestWithContext(ctx, "GET", baseURL+ep.path, nil)
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil {
			continue
		}
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 50*1024))
		resp.Body.Close()
		bodyStr := string(body)
		bodyLower := strings.ToLower(bodyStr)

		result := OdintErrorPageResult{StatusCode: resp.StatusCode}

		// Server info
		if server := resp.Header.Get("Server"); server != "" {
			result.ServerInfo = server
		}

		// Stack trace detection
		if strings.Contains(bodyLower, "traceback") || strings.Contains(bodyLower, "stack trace") ||
			strings.Contains(bodyLower, "at line") || strings.Contains(bodyStr, "Exception in") {
			result.StackTrace = true
		}

		// Internal IPs
		ipRe := regexp.MustCompile(`(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})`)
		result.InternalIPs = ipRe.FindAllString(bodyStr, 10)

		// File paths
		pathRe := regexp.MustCompile(`(?:/[a-zA-Z0-9_.-]+){3,}\.(?:py|rb|php|js|go|java|cs|asp)`)
		result.FilePaths = pathRe.FindAllString(bodyStr, 10)

		// Database errors
		dbPatterns := []string{"sql", "mysql", "postgresql", "mongodb", "sqlite", "database error", "query failed"}
		for _, pat := range dbPatterns {
			if strings.Contains(bodyLower, pat) {
				result.DBErrors = append(result.DBErrors, pat)
			}
		}

		// Framework detection from error page
		frameworkPatterns := map[string]string{
			"django": "Django", "laravel": "Laravel", "rails": "Ruby on Rails",
			"express": "Express", "flask": "Flask", "spring": "Spring",
			"asp.net": "ASP.NET", "symfony": "Symfony",
		}
		for pat, name := range frameworkPatterns {
			if strings.Contains(bodyLower, pat) {
				result.Framework = name
				break
			}
		}

		if result.StackTrace || len(result.InternalIPs) > 0 || len(result.FilePaths) > 0 || len(result.DBErrors) > 0 {
			results = append(results, result)
		}
	}
	return results
}

func (tui *TUI) odintStoreErrorPages(domain string, pages []OdintErrorPageResult) {
	for _, p := range pages {
		stackTrace := 0
		if p.StackTrace {
			stackTrace = 1
		}
		tui.db.conn.Exec(`INSERT INTO odint_error_pages
			(domain, status_code, server_info, stack_trace, internal_ips, dev_names, file_paths, db_errors, framework, discovered)
			VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, p.StatusCode, p.ServerInfo, stackTrace,
			strings.Join(p.InternalIPs, ","), strings.Join(p.DevNames, ","),
			strings.Join(p.FilePaths, ","), strings.Join(p.DBErrors, ","), p.Framework)
	}
}

// ===========================================================================
// PHASE 4: VIRTUAL HOST ENUMERATION
// ===========================================================================

func odintEnumerateVirtualHosts(ctx context.Context, client *http.Client, domain string) []OdintVirtualHostInfo {
	var results []OdintVirtualHostInfo

	// Resolve domain to IP
	ips, err := net.LookupHost(domain)
	if err != nil || len(ips) == 0 {
		return nil
	}
	ip := ips[0]

	// HackerTarget reverse IP lookup (free)
	apiURL := fmt.Sprintf("https://api.hackertarget.com/reverseiplookup/?q=%s", url.QueryEscape(ip))
	reqCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
	defer cancel()
	req, err := http.NewRequestWithContext(reqCtx, "GET", apiURL, nil)
	if err != nil {
		return nil
	}
	req.Header.Set("User-Agent", UserAgent)
	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 100*1024))
	resp.Body.Close()

	seen := make(map[string]bool)
	for _, line := range strings.Split(string(body), "\n") {
		line = strings.TrimSpace(line)
		if line == "" || strings.Contains(line, "error") || strings.Contains(line, "API") || seen[line] {
			continue
		}
		seen[line] = true
		results = append(results, OdintVirtualHostInfo{
			Domain:    line,
			IP:        ip,
			Source:    "hackertarget",
			IsDefault: line == domain,
		})
	}
	return results
}

func (tui *TUI) odintStoreVirtualHosts(domain string, vhosts []OdintVirtualHostInfo) {
	for _, vh := range vhosts {
		isDefault := 0
		if vh.IsDefault {
			isDefault = 1
		}
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_virtual_hosts
			(domain, vhost_domain, ip, source, is_default, status_code, discovered)
			VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, vh.Domain, vh.IP, vh.Source, isDefault, vh.StatusCode)
	}
}

// ===========================================================================
// PHASE 5: EXTERNAL API INTEGRATIONS
// ===========================================================================

// HIBP Breach Lookup
func odintHIBPLookup(ctx context.Context, client *http.Client, email, apiKey string) []OdintHIBPBreach {
	if apiKey == "" {
		return nil
	}
	apiURL := fmt.Sprintf("https://haveibeenpwned.com/api/v3/breachedaccount/%s?truncateResponse=false", url.PathEscape(email))
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("hibp-api-key", apiKey)
	req.Header.Set("User-Agent", UserAgent)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 1*1024*1024))
	resp.Body.Close()

	var breaches []struct {
		Name        string   `json:"Name"`
		BreachDate  string   `json:"BreachDate"`
		DataClasses []string `json:"DataClasses"`
		PwnCount    int      `json:"PwnCount"`
	}
	if json.Unmarshal(body, &breaches) != nil {
		return nil
	}

	var results []OdintHIBPBreach
	for _, b := range breaches {
		results = append(results, OdintHIBPBreach{
			Email: email, BreachName: b.Name, BreachDate: b.BreachDate,
			DataClasses: b.DataClasses, PwnCount: b.PwnCount,
		})
	}
	return results
}

// GitHub OSINT Search
func odintGithubSearch(ctx context.Context, client *http.Client, domain, token string) []OdintGithubFinding {
	if token == "" {
		return nil
	}
	var findings []OdintGithubFinding
	queries := []string{
		fmt.Sprintf(`"%s" password OR secret OR api_key`, domain),
		fmt.Sprintf(`"%s" filename:.env`, domain),
	}

	for _, q := range queries {
		if ctx.Err() != nil {
			break
		}
		apiURL := fmt.Sprintf("https://api.github.com/search/code?q=%s&per_page=10", url.QueryEscape(q))
		req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
		req.Header.Set("Authorization", "token "+token)
		req.Header.Set("Accept", "application/vnd.github.v3+json")
		resp, err := client.Do(req)
		if err != nil || resp.StatusCode != 200 {
			if resp != nil {
				resp.Body.Close()
			}
			continue
		}
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
		resp.Body.Close()

		var result struct {
			Items []struct {
				Name    string `json:"name"`
				HTMLURL string `json:"html_url"`
				Repo    struct {
					FullName string `json:"full_name"`
				} `json:"repository"`
			} `json:"items"`
		}
		if json.Unmarshal(body, &result) == nil {
			for _, item := range result.Items {
				findings = append(findings, OdintGithubFinding{
					Type: "code", Name: item.Name, URL: item.HTMLURL,
					Snippet: item.Repo.FullName,
				})
			}
		}
		time.Sleep(2 * time.Second) // GitHub rate limit
	}
	return findings
}

// Google Dorks Generation (instant, no API)
func odintGenerateDorks(domain string) []string {
	return []string{
		fmt.Sprintf(`site:%s filetype:pdf`, domain),
		fmt.Sprintf(`site:%s filetype:doc OR filetype:docx`, domain),
		fmt.Sprintf(`site:%s filetype:xls OR filetype:xlsx`, domain),
		fmt.Sprintf(`site:%s filetype:sql OR filetype:db`, domain),
		fmt.Sprintf(`site:%s filetype:log`, domain),
		fmt.Sprintf(`site:%s filetype:env`, domain),
		fmt.Sprintf(`site:%s inurl:admin`, domain),
		fmt.Sprintf(`site:%s inurl:login`, domain),
		fmt.Sprintf(`site:%s inurl:api`, domain),
		fmt.Sprintf(`site:%s intitle:"index of"`, domain),
		fmt.Sprintf(`site:%s ext:php intitle:phpinfo`, domain),
		fmt.Sprintf(`site:%s "not for distribution"`, domain),
		fmt.Sprintf(`site:%s "confidential"`, domain),
		fmt.Sprintf(`"%s" password OR secret filetype:txt`, domain),
		fmt.Sprintf(`"%s" site:pastebin.com OR site:paste.ee`, domain),
		fmt.Sprintf(`"%s" site:trello.com`, domain),
	}
}

func (tui *TUI) odintStoreDorks(domain string, dorks []string) {
	for _, d := range dorks {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_google_dorks
			(domain, dork, category, discovered)
			VALUES (?, ?, 'auto', CURRENT_TIMESTAMP)`, domain, d)
	}
}

// Social Media Profile Check
func odintCheckSocialMedia(ctx context.Context, client *http.Client, domain string) []OdintSocialProfile {
	var profiles []OdintSocialProfile
	name := strings.Split(domain, ".")[0]

	platforms := map[string]string{
		"Twitter":    fmt.Sprintf("https://twitter.com/%s", name),
		"GitHub":     fmt.Sprintf("https://github.com/%s", name),
		"LinkedIn":   fmt.Sprintf("https://www.linkedin.com/company/%s", name),
		"Facebook":   fmt.Sprintf("https://www.facebook.com/%s", name),
		"Instagram":  fmt.Sprintf("https://www.instagram.com/%s", name),
		"YouTube":    fmt.Sprintf("https://www.youtube.com/@%s", name),
		"Reddit":     fmt.Sprintf("https://www.reddit.com/r/%s", name),
		"Medium":     fmt.Sprintf("https://medium.com/@%s", name),
		"Pinterest":  fmt.Sprintf("https://www.pinterest.com/%s", name),
		"TikTok":     fmt.Sprintf("https://www.tiktok.com/@%s", name),
	}

	for platform, profileURL := range platforms {
		if ctx.Err() != nil {
			break
		}
		req, _ := http.NewRequestWithContext(ctx, "HEAD", profileURL, nil)
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil {
			continue
		}
		resp.Body.Close()
		profiles = append(profiles, OdintSocialProfile{
			Platform: platform, URL: profileURL, Username: name,
			Exists: resp.StatusCode == 200, Status: resp.StatusCode,
		})
	}
	return profiles
}

func (tui *TUI) odintStoreSocialProfiles(domain string, profiles []OdintSocialProfile) {
	for _, p := range profiles {
		existsFlag := 0
		if p.Exists {
			existsFlag = 1
		}
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_social_profiles
			(domain, platform, url, username, exists_flag, status_code, discovered)
			VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, p.Platform, p.URL, p.Username, existsFlag, p.Status)
	}
}

// Cloud Storage Bucket Check
func odintCheckCloudStorage(ctx context.Context, client *http.Client, domain string) []OdintCloudExposure {
	var results []OdintCloudExposure
	name := strings.Split(domain, ".")[0]

	buckets := []struct {
		provider string
		urlFmt   string
	}{
		{"AWS S3", "https://%s.s3.amazonaws.com"},
		{"AWS S3", "https://s3.amazonaws.com/%s"},
		{"Azure Blob", "https://%s.blob.core.windows.net"},
		{"Google Cloud", "https://storage.googleapis.com/%s"},
	}

	for _, b := range buckets {
		if ctx.Err() != nil {
			break
		}
		bucketURL := fmt.Sprintf(b.urlFmt, name)
		req, _ := http.NewRequestWithContext(ctx, "HEAD", bucketURL, nil)
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil {
			continue
		}
		resp.Body.Close()
		results = append(results, OdintCloudExposure{
			Provider: b.provider, BucketName: name, URL: bucketURL,
			Status: resp.StatusCode, Accessible: resp.StatusCode == 200,
		})
	}
	return results
}

func (tui *TUI) odintStoreCloudExposures(domain string, exposures []OdintCloudExposure) {
	for _, e := range exposures {
		accessible := 0
		if e.Accessible {
			accessible = 1
		}
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_cloud_exposures
			(domain, provider, bucket_name, url, status_code, accessible, discovered)
			VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, e.Provider, e.BucketName, e.URL, e.Status, accessible)
	}
}

// Network Intelligence (ip-api.com, free)
func odintLookupNetworkIntel(ctx context.Context, client *http.Client, ip string) *OdintNetworkIntelResult {
	if ip == "" {
		return nil
	}
	apiURL := fmt.Sprintf("http://ip-api.com/json/%s", ip)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 10*1024))
	resp.Body.Close()

	var data struct {
		Status  string  `json:"status"`
		Country string  `json:"country"`
		City    string  `json:"city"`
		ISP     string  `json:"isp"`
		Org     string  `json:"org"`
		AS      string  `json:"as"`
		Lat     float64 `json:"lat"`
		Lon     float64 `json:"lon"`
	}
	if json.Unmarshal(body, &data) != nil || data.Status != "success" {
		return nil
	}

	return &OdintNetworkIntelResult{
		ASN: data.AS, ASNOrg: data.Org, Country: data.Country,
		City: data.City, ISP: data.ISP, Lat: data.Lat, Lon: data.Lon,
	}
}

func (tui *TUI) odintStoreNetworkIntel(domain, ip string, intel *OdintNetworkIntelResult) {
	if intel == nil {
		return
	}
	tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_network_intel
		(domain, ip, asn, asn_org, bgp_prefix, rir, country, city, isp, lat, lon, discovered)
		VALUES (?, ?, ?, ?, ?, '', ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
		domain, ip, intel.ASN, intel.ASNOrg, intel.BGPPrefix,
		intel.Country, intel.City, intel.ISP, intel.Lat, intel.Lon)
}

// ===========================================================================
// PHASE 5: EXTERNAL APIS V2 (all require keys, skip gracefully)
// ===========================================================================

func odintCensysSearch(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasCensysKeys() {
		return nil
	}
	apiURL := fmt.Sprintf("https://search.censys.io/api/v2/hosts/search?q=%s&per_page=5", url.QueryEscape(hostname))
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.SetBasicAuth(cfg.CensysAPIID, cfg.CensysAPISecret)
	req.Header.Set("Accept", "application/json")
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintVirusTotalLookup(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasVirusTotalKey() {
		return nil
	}
	apiURL := fmt.Sprintf("https://www.virustotal.com/api/v3/domains/%s", hostname)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("x-apikey", cfg.VirusTotalKey)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintSecurityTrailsLookup(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasSecurityTrailsKey() {
		return nil
	}
	apiURL := fmt.Sprintf("https://api.securitytrails.com/v1/domain/%s", hostname)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("APIKEY", cfg.SecurityTrailsKey)
	req.Header.Set("Accept", "application/json")
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintHunterIOSearch(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasHunterIOKey() {
		return nil
	}
	apiURL := fmt.Sprintf("https://api.hunter.io/v2/domain-search?domain=%s&api_key=%s", hostname, cfg.HunterIOKey)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintBGPViewLookup(ctx context.Context, client *http.Client, ip string) map[string]interface{} {
	apiURL := fmt.Sprintf("https://api.bgpview.io/ip/%s", ip)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("Accept", "application/json")
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 100*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintIPInfoLookup(ctx context.Context, client *http.Client, ip string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasIPInfoToken() {
		return nil
	}
	apiURL := fmt.Sprintf("https://ipinfo.io/%s?token=%s", ip, cfg.IPInfoToken)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 50*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintGreyNoiseLookup(ctx context.Context, client *http.Client, ip string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasGreyNoiseKey() {
		// Try community API (free, no key)
		apiURL := fmt.Sprintf("https://api.greynoise.io/v3/community/%s", ip)
		req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
		req.Header.Set("Accept", "application/json")
		resp, err := client.Do(req)
		if err != nil || resp.StatusCode != 200 {
			if resp != nil {
				resp.Body.Close()
			}
			return nil
		}
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 50*1024))
		resp.Body.Close()
		var result map[string]interface{}
		json.Unmarshal(body, &result)
		return result
	}
	apiURL := fmt.Sprintf("https://api.greynoise.io/v3/community/%s", ip)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("key", cfg.GreyNoiseKey)
	req.Header.Set("Accept", "application/json")
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 50*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintBinaryEdgeLookup(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasBinaryEdgeKey() {
		return nil
	}
	apiURL := fmt.Sprintf("https://api.binaryedge.io/v2/query/domains/subdomain/%s", hostname)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("X-Key", cfg.BinaryEdgeKey)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintZoomEyeSearch(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasZoomEyeKey() {
		return nil
	}
	apiURL := fmt.Sprintf("https://api.zoomeye.org/host/search?query=hostname:%s", url.QueryEscape(hostname))
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	req.Header.Set("API-KEY", cfg.ZoomEyeKey)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

func odintFOFASearch(ctx context.Context, client *http.Client, hostname string, cfg OdintConfig) map[string]interface{} {
	if !cfg.HasFOFAKeys() {
		return nil
	}
	query := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`domain="%s"`, hostname)))
	apiURL := fmt.Sprintf("https://fofa.info/api/v1/search/all?email=%s&key=%s&qbase64=%s&size=10",
		cfg.FOFAEmail, cfg.FOFAKey, query)
	req, _ := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
	resp, err := client.Do(req)
	if err != nil || resp.StatusCode != 200 {
		if resp != nil {
			resp.Body.Close()
		}
		return nil
	}
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
	resp.Body.Close()
	var result map[string]interface{}
	json.Unmarshal(body, &result)
	return result
}

// Store API v2 results as findings
func (tui *TUI) odintStoreAPIv2Result(domain, apiName string, data map[string]interface{}) {
	if data == nil {
		return
	}
	jsonData, _ := json.Marshal(data)
	summary := string(jsonData)
	if len(summary) > 500 {
		summary = summary[:497] + "..."
	}
	tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_findings
		(domain, finding_type, title, detail, severity, source_module, discovered)
		VALUES (?, 'api', ?, ?, 'info', ?, CURRENT_TIMESTAMP)`,
		domain, apiName+" data collected", summary, apiName)
}

// ===========================================================================
// PHASE 6: FILE DISCOVERY & DOWNLOAD
// ===========================================================================

func odintDiscoverFiles(body, baseURL string) []OdintDiscoveredFile {
	var files []OdintDiscoveredFile
	seen := make(map[string]bool)

	// Extract file links from HTML
	linkRe := regexp.MustCompile(`(?i)(?:href|src)=["']([^"']+\.(?:pdf|doc|docx|xls|xlsx|csv|txt|zip|tar|gz|rar|7z|sql|db|bak|log|xml|json|yaml|yml|conf|config|png|jpg|jpeg|gif|svg|mp4|mp3|wav|avi|mov))["']`)
	for _, match := range linkRe.FindAllStringSubmatch(body, 200) {
		if len(match) < 2 {
			continue
		}
		fileURL := match[1]
		if !strings.HasPrefix(fileURL, "http") {
			if strings.HasPrefix(fileURL, "/") {
				fileURL = baseURL + fileURL
			} else {
				fileURL = baseURL + "/" + fileURL
			}
		}
		if seen[fileURL] {
			continue
		}
		seen[fileURL] = true

		filename := filepath.Base(fileURL)
		ext := strings.ToLower(filepath.Ext(filename))
		fileType := odintCategorizeFile(ext)

		files = append(files, OdintDiscoveredFile{
			URL: fileURL, Filename: filename, FileType: fileType, Source: "html",
		})
	}
	return files
}

func odintCategorizeFile(ext string) string {
	switch ext {
	case ".pdf":
		return "pdf"
	case ".doc", ".docx", ".odt", ".rtf":
		return "document"
	case ".xls", ".xlsx", ".csv", ".ods":
		return "spreadsheet"
	case ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".bmp", ".ico":
		return "image"
	case ".mp4", ".avi", ".mov", ".wmv", ".flv":
		return "video"
	case ".mp3", ".wav", ".ogg", ".flac":
		return "audio"
	case ".zip", ".tar", ".gz", ".rar", ".7z":
		return "archive"
	case ".sql", ".db", ".sqlite", ".bak":
		return "database"
	case ".xml", ".json", ".yaml", ".yml":
		return "data"
	case ".txt", ".log", ".conf", ".config":
		return "text"
	default:
		return "other"
	}
}

func (tui *TUI) odintStoreDiscoveredFiles(domain string, files []OdintDiscoveredFile) {
	for _, f := range files {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_discovered_files
			(domain, url, filename, file_type, source, discovered)
			VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, f.URL, f.Filename, f.FileType, f.Source)
	}
}

func (tui *TUI) odintDownloadFiles(ctx context.Context, client *http.Client, domain string, files []OdintDiscoveredFile) (success, failed int) {
	exe, _ := os.Executable()
	vaultDir := filepath.Join(filepath.Dir(exe), "vault", domain)
	os.MkdirAll(vaultDir, 0755)

	sourceListPath := filepath.Join(vaultDir, "sourcelist.txt")
	sourceFile, err := os.OpenFile(sourceListPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return 0, len(files)
	}
	defer sourceFile.Close()

	cfg := odintLoadConfig()
	maxSize := int64(50 * 1024 * 1024) // 50MB default
	if cfg.MaxDownloadSizeMB > 0 {
		maxSize = int64(cfg.MaxDownloadSizeMB) * 1024 * 1024
	}

	atomic.StoreInt64(&tui.odintDownloadTotal, int64(len(files)))
	atomic.StoreInt64(&tui.odintDownloadDone, 0)

	for _, f := range files {
		if ctx.Err() != nil {
			break
		}
		atomic.AddInt64(&tui.odintDownloadDone, 1)

		req, _ := http.NewRequestWithContext(ctx, "GET", f.URL, nil)
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil || resp.StatusCode != 200 {
			if resp != nil {
				resp.Body.Close()
			}
			failed++
			continue
		}

		// Create category subdirectory
		catDir := filepath.Join(vaultDir, f.FileType)
		os.MkdirAll(catDir, 0755)

		data, err := io.ReadAll(io.LimitReader(resp.Body, maxSize))
		resp.Body.Close()
		if err != nil {
			failed++
			continue
		}

		localPath := filepath.Join(catDir, f.Filename)
		if err := os.WriteFile(localPath, data, 0644); err != nil {
			failed++
			continue
		}

		// SHA256 chain of custody
		hash := sha256.Sum256(data)
		hashStr := hex.EncodeToString(hash[:])
		entry := fmt.Sprintf("%s | %s | %s | %s\n", time.Now().Format("2006-01-02 15:04:05"), hashStr, f.URL, localPath)
		sourceFile.WriteString(entry)

		// Update DB
		tui.db.conn.Exec(`UPDATE odint_discovered_files SET sha256 = ?, downloaded = 1 WHERE domain = ? AND url = ?`,
			hashStr, domain, f.URL)
		success++
	}
	return
}

// ===========================================================================
// PHASE 7: HTML/JSON REPORT GENERATION
// ===========================================================================

func (tui *TUI) odintGenerateHTMLReport(domain string) string {
	timestamp := time.Now().Format("2006-01-02_150405")
	reportPath := filepath.Join(tui.logsDir, fmt.Sprintf("odint_report_%s_%s.html", domain, timestamp))

	var html strings.Builder
	html.WriteString(`<!DOCTYPE html><html><head><meta charset="utf-8">`)
	html.WriteString(fmt.Sprintf(`<title>ODINT Report - %s</title>`, domain))
	html.WriteString(`<style>
		body{font-family:monospace;background:#1a1a2e;color:#e0e0e0;padding:20px;max-width:1200px;margin:0 auto}
		h1{color:#ff4444;border-bottom:2px solid #ff4444;padding-bottom:10px}
		h2{color:#ff8c1a;margin-top:30px}
		h3{color:#00c8dc}
		table{border-collapse:collapse;width:100%;margin:10px 0}
		th,td{border:1px solid #333;padding:8px;text-align:left}
		th{background:#2a2a4a;color:#ff8c1a}
		tr:nth-child(even){background:#1a1a3e}
		.critical{color:#ff0000;font-weight:bold}
		.high{color:#ff4444}
		.medium{color:#ff8c1a}
		.low{color:#00c8dc}
		.info{color:#64b4ff}
		.stat{display:inline-block;background:#2a2a4a;padding:10px 20px;margin:5px;border-radius:5px;border:1px solid #333}
		.stat-num{font-size:24px;font-weight:bold;color:#ff4444}
		pre{background:#0a0a1e;padding:10px;border-radius:5px;overflow-x:auto}
		</style></head><body>`)
	html.WriteString(fmt.Sprintf(`<h1>ODINT Reconnaissance Report: %s</h1>`, domain))
	html.WriteString(fmt.Sprintf(`<p>Generated: %s</p>`, time.Now().Format("2006-01-02 15:04:05")))

	// Statistics
	html.WriteString(`<h2>Executive Summary</h2><div>`)
	stats := map[string]string{
		"odint_findings":     "Findings",
		"odint_technologies": "Technologies",
		"odint_dns":          "DNS Records",
		"odint_subdomains":   "Subdomains",
		"odint_exposed_paths": "Exposed Paths",
		"odint_secrets":       "Secrets",
		"odint_nmap_results":  "Open Ports",
		"odint_cve_findings":  "CVEs",
	}
	for table, label := range stats {
		var count int
		tui.db.conn.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE domain = ?", table), domain).Scan(&count)
		html.WriteString(fmt.Sprintf(`<div class="stat"><div class="stat-num">%d</div>%s</div>`, count, label))
	}
	html.WriteString(`</div>`)

	// Findings
	html.WriteString(`<h2>Security Findings</h2><table><tr><th>Type</th><th>Title</th><th>Severity</th><th>Module</th></tr>`)
	rows, _ := tui.db.conn.Query(`SELECT finding_type, title, detail, severity, source_module FROM odint_findings WHERE domain = ? ORDER BY
		CASE severity WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 WHEN 'low' THEN 4 ELSE 5 END`, domain)
	if rows != nil {
		defer rows.Close()
		for rows.Next() {
			var fType, title, detail, severity, module string
			rows.Scan(&fType, &title, &detail, &severity, &module)
			html.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%s<br><small>%s</small></td><td class="%s">%s</td><td>%s</td></tr>`,
				fType, title, detail, severity, strings.ToUpper(severity), module))
		}
	}
	html.WriteString(`</table>`)

	// Technologies
	html.WriteString(`<h2>Detected Technologies</h2><table><tr><th>Name</th><th>Category</th></tr>`)
	rows2, _ := tui.db.conn.Query(`SELECT tech_name, tech_category FROM odint_technologies WHERE domain = ? ORDER BY tech_category`, domain)
	if rows2 != nil {
		defer rows2.Close()
		for rows2.Next() {
			var name, cat string
			rows2.Scan(&name, &cat)
			html.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%s</td></tr>`, name, cat))
		}
	}
	html.WriteString(`</table>`)

	// Nmap results
	html.WriteString(`<h2>Port Scan Results</h2><table><tr><th>Port</th><th>Protocol</th><th>Service</th><th>Version</th></tr>`)
	rows3, _ := tui.db.conn.Query(`SELECT port, protocol, service, version, product FROM odint_nmap_results WHERE domain = ? ORDER BY port`, domain)
	if rows3 != nil {
		defer rows3.Close()
		for rows3.Next() {
			var port int
			var proto, svc, ver, prod string
			rows3.Scan(&port, &proto, &svc, &ver, &prod)
			html.WriteString(fmt.Sprintf(`<tr><td>%d</td><td>%s</td><td>%s</td><td>%s %s</td></tr>`, port, proto, svc, prod, ver))
		}
	}
	html.WriteString(`</table>`)

	// CVEs
	html.WriteString(`<h2>CVE Findings</h2><table><tr><th>CVE</th><th>Score</th><th>Severity</th><th>Description</th></tr>`)
	rows4, _ := tui.db.conn.Query(`SELECT cve_id, cvss_score, severity, description FROM odint_cve_findings WHERE domain = ? ORDER BY cvss_score DESC`, domain)
	if rows4 != nil {
		defer rows4.Close()
		for rows4.Next() {
			var cveID, severity, desc string
			var score float64
			rows4.Scan(&cveID, &score, &severity, &desc)
			html.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%.1f</td><td class="%s">%s</td><td>%s</td></tr>`,
				cveID, score, strings.ToLower(severity), severity, desc))
		}
	}
	html.WriteString(`</table>`)

	// Subdomains
	html.WriteString(`<h2>Subdomains</h2><table><tr><th>Subdomain</th><th>IP</th><th>Source</th></tr>`)
	rows5, _ := tui.db.conn.Query(`SELECT subdomain, ip, source FROM odint_subdomains WHERE domain = ?`, domain)
	if rows5 != nil {
		defer rows5.Close()
		for rows5.Next() {
			var sub, ip, src string
			rows5.Scan(&sub, &ip, &src)
			html.WriteString(fmt.Sprintf(`<tr><td>%s</td><td>%s</td><td>%s</td></tr>`, sub, ip, src))
		}
	}
	html.WriteString(`</table>`)

	// Secrets
	html.WriteString(`<h2>Discovered Secrets</h2><table><tr><th>Type</th><th>Value</th><th>Severity</th></tr>`)
	rows6, _ := tui.db.conn.Query(`SELECT secret_type, value, severity FROM odint_secrets WHERE domain = ? ORDER BY severity`, domain)
	if rows6 != nil {
		defer rows6.Close()
		for rows6.Next() {
			var sType, val, severity string
			rows6.Scan(&sType, &val, &severity)
			html.WriteString(fmt.Sprintf(`<tr><td>%s</td><td><pre>%s</pre></td><td class="%s">%s</td></tr>`,
				sType, val, severity, strings.ToUpper(severity)))
		}
	}
	html.WriteString(`</table>`)

	html.WriteString(`<hr><p style="color:#666">Generated by TERMINATOR BOT 9000 - ODINT Toolkit v4.0.0</p>`)
	html.WriteString(`</body></html>`)

	os.WriteFile(reportPath, []byte(html.String()), 0644)
	return reportPath
}

func (tui *TUI) odintGenerateJSONReport(domain string) string {
	timestamp := time.Now().Format("2006-01-02_150405")
	reportPath := filepath.Join(tui.logsDir, fmt.Sprintf("odint_report_%s_%s.json", domain, timestamp))

	report := make(map[string]interface{})
	report["domain"] = domain
	report["generated"] = time.Now().Format("2006-01-02 15:04:05")
	report["generator"] = "TERMINATOR BOT 9000 - ODINT Toolkit v4.0.0"

	// Collect data from all tables
	tableQueries := map[string]string{
		"findings":     "SELECT finding_type, title, detail, severity, source_module FROM odint_findings WHERE domain = ?",
		"technologies": "SELECT tech_name, tech_category FROM odint_technologies WHERE domain = ?",
		"dns":          "SELECT record_type, record_value FROM odint_dns WHERE domain = ?",
		"ssl":          "SELECT issuer, subject, serial, not_before, not_after, sig_algorithm, key_size FROM odint_ssl WHERE domain = ?",
		"subdomains":   "SELECT subdomain, source, ip FROM odint_subdomains WHERE domain = ?",
		"nmap":         "SELECT port, protocol, service, version, product FROM odint_nmap_results WHERE domain = ?",
		"cves":         "SELECT cve_id, cvss_score, severity, description FROM odint_cve_findings WHERE domain = ?",
		"secrets":      "SELECT secret_type, value, severity FROM odint_secrets WHERE domain = ?",
		"exposed_paths": "SELECT path, status_code, path_type FROM odint_exposed_paths WHERE domain = ?",
	}

	for key, query := range tableQueries {
		rows, err := tui.db.conn.Query(query, domain)
		if err != nil {
			continue
		}
		cols, _ := rows.Columns()
		var entries []map[string]interface{}
		for rows.Next() {
			vals := make([]interface{}, len(cols))
			ptrs := make([]interface{}, len(cols))
			for i := range vals {
				ptrs[i] = &vals[i]
			}
			if rows.Scan(ptrs...) == nil {
				entry := make(map[string]interface{})
				for i, col := range cols {
					entry[col] = vals[i]
				}
				entries = append(entries, entry)
			}
		}
		rows.Close()
		report[key] = entries
	}

	data, _ := json.MarshalIndent(report, "", "  ")
	os.WriteFile(reportPath, data, 0644)
	return reportPath
}

// ===========================================================================
// UTILITY — appendOdintUnique helper
// ===========================================================================

func appendOdintUnique(slice []string, val string) []string {
	for _, s := range slice {
		if s == val {
			return slice
		}
	}
	return append(slice, val)
}

// ===========================================================================
// GAP FILL: HASH COLLECTION — collect hashes from page content
// ===========================================================================

func odintCollectHashes(body, domain string) []OdintHashCorrelation {
	var results []OdintHashCorrelation
	seen := make(map[string]bool)

	addHash := func(hash, hashType, source string) {
		if seen[hash] {
			return
		}
		seen[hash] = true
		results = append(results, OdintHashCorrelation{Hash: hash, HashType: hashType, Source: source, Domain: domain})
	}

	// Gravatar MD5 hashes
	gravatarRe := regexp.MustCompile(`(?i)gravatar\.com/avatar/([a-f0-9]{32})`)
	for _, m := range gravatarRe.FindAllStringSubmatch(body, 50) {
		if len(m) > 1 {
			addHash(m[1], "md5", "gravatar")
		}
	}

	// Subresource Integrity (SRI) hashes
	sriRe := regexp.MustCompile(`integrity="(sha(?:256|384|512)-[A-Za-z0-9+/=]+)"`)
	for _, m := range sriRe.FindAllStringSubmatch(body, 50) {
		if len(m) > 1 {
			addHash(m[1], "sri", "integrity_attr")
		}
	}

	// Hex hashes in context (MD5 = 32 hex, SHA1 = 40 hex, SHA256 = 64 hex)
	hexRe := regexp.MustCompile(`(?i)(?:hash|checksum|md5|sha1|sha256|etag)['":\s=]+([a-f0-9]{32,64})`)
	for _, m := range hexRe.FindAllStringSubmatch(body, 30) {
		if len(m) > 1 {
			h := strings.ToLower(m[1])
			switch len(h) {
			case 32:
				addHash(h, "md5", "page_content")
			case 40:
				addHash(h, "sha1", "page_content")
			case 64:
				addHash(h, "sha256", "page_content")
			}
		}
	}

	return results
}

func (tui *TUI) odintStoreHashCorrelations(domain string, hashes []OdintHashCorrelation) {
	for _, h := range hashes {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_hash_correlations
			(domain, hash, hash_type, source, discovered) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, h.Hash, h.HashType, h.Source)
	}
}

// ===========================================================================
// GAP FILL: COOKIE FINGERPRINTING — map cookies to technologies
// ===========================================================================

func odintExtractCookieFingerprints(cookies []*http.Cookie) []OdintCookieFingerprint {
	cookieTechMap := map[string]string{
		"PHPSESSID": "PHP", "JSESSIONID": "Java", "ASP.NET_SessionId": "ASP.NET",
		".AspNetCore.": "ASP.NET Core", "laravel_session": "Laravel", "XSRF-TOKEN": "Laravel/Angular",
		"csrftoken": "Django", "connect.sid": "Express.js", "rack.session": "Ruby on Rails",
		"_session_id": "Rails", "ci_session": "CodeIgniter", "cakephp": "CakePHP",
		"symfony": "Symfony", "wp-settings": "WordPress", "wordpress_logged_in": "WordPress",
		"SERVERID": "HAProxy", "BIGipServer": "F5 BIG-IP", "AWSALB": "AWS ALB",
		"AWSALBCORS": "AWS ALB", "FORTIWAFSID": "FortiWeb WAF", "incap_ses_": "Imperva",
		"visid_incap_": "Imperva", "__cfduid": "Cloudflare", "__cf_bm": "Cloudflare Bot Mgmt",
	}

	var results []OdintCookieFingerprint
	for _, c := range cookies {
		for pattern, tech := range cookieTechMap {
			if strings.Contains(strings.ToLower(c.Name), strings.ToLower(pattern)) {
				flags := ""
				if c.Secure {
					flags += "Secure "
				}
				if c.HttpOnly {
					flags += "HttpOnly "
				}
				if c.SameSite != 0 {
					flags += fmt.Sprintf("SameSite=%d", c.SameSite)
				}
				val := c.Value
				if len(val) > 30 {
					val = val[:30] + "..."
				}
				results = append(results, OdintCookieFingerprint{
					Name: c.Name, Value: val, Flags: strings.TrimSpace(flags), TechMatch: tech,
				})
				break
			}
		}
	}
	return results
}

func (tui *TUI) odintStoreCookieFingerprints(domain string, fps []OdintCookieFingerprint) {
	for _, f := range fps {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_cookie_fingerprints
			(domain, cookie_name, cookie_value, flags, tech_match, discovered) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, f.Name, f.Value, f.Flags, f.TechMatch)
	}
}

// ===========================================================================
// GAP FILL: MANAGEMENT UI DETECTION — probe admin panels
// ===========================================================================

func odintDetectManagementUIs(ctx context.Context, client *http.Client, baseURL string) []OdintManagementUI {
	mgmtPaths := []struct {
		path    string
		uiType  string
	}{
		{"/phpmyadmin/", "phpMyAdmin"}, {"/pma/", "phpMyAdmin"}, {"/adminer.php", "Adminer"},
		{"/pgadmin/", "pgAdmin"}, {"/mongo-express/", "Mongo Express"}, {"/redis-commander", "Redis Commander"},
		{"/kibana/", "Kibana"}, {"/grafana/", "Grafana"}, {"/prometheus/", "Prometheus"},
		{"/jenkins/", "Jenkins"}, {"/airflow/", "Apache Airflow"}, {"/flower/", "Celery Flower"},
		{"/sidekiq/", "Sidekiq"}, {"/hangfire/", "Hangfire"}, {"/jupyter/", "Jupyter"},
		{"/mlflow/", "MLflow"}, {"/metabase/", "Metabase"}, {"/redash/", "Redash"},
		{"/superset/", "Superset"}, {"/portainer/", "Portainer"}, {"/traefik/", "Traefik"},
		{"/consul/", "Consul"}, {"/vault/", "Vault"}, {"/nomad/", "Nomad"},
		{"/rancher/", "Rancher"}, {"/zeppelin/", "Zeppelin"}, {"/geoserver/", "GeoServer"},
		{"/solr/", "Solr"}, {"/elasticsearch/", "Elasticsearch"}, {"/cPanel/", "cPanel"},
		{"/plesk/", "Plesk"}, {"/webmin/", "Webmin"}, {"/cockpit/", "Cockpit"},
		{"/sonarqube/", "SonarQube"}, {"/nexus/", "Nexus"}, {"/artifactory/", "Artifactory"},
		{"/argo/", "Argo CD"}, {"/drone/", "Drone CI"}, {"/rundeck/", "Rundeck"},
		{"/awx/", "AWX/Tower"}, {"/longhorn/", "Longhorn"}, {"/minio/", "MinIO"},
		{"/proxmox/", "Proxmox"}, {"/vmware/", "VMware"}, {"/openshift/", "OpenShift"},
		{"/harbor/", "Harbor"}, {"/argocd/", "Argo CD"}, {"/tekton/", "Tekton"},
		{"/sentry/", "Sentry"}, {"/gitea/", "Gitea"}, {"/gogs/", "Gogs"},
	}

	var results []OdintManagementUI
	var mu sync.Mutex
	sem := make(chan struct{}, 10)
	var wg sync.WaitGroup

	for _, p := range mgmtPaths {
		if ctx.Err() != nil {
			break
		}
		wg.Add(1)
		sem <- struct{}{}
		go func(path, uiType string) {
			defer wg.Done()
			defer func() { <-sem }()
			req, err := http.NewRequestWithContext(ctx, "HEAD", baseURL+path, nil)
			if err != nil {
				return
			}
			req.Header.Set("User-Agent", UserAgent)
			resp, err := client.Do(req)
			if err != nil {
				return
			}
			resp.Body.Close()
			if resp.StatusCode == 200 || resp.StatusCode == 401 || resp.StatusCode == 403 || resp.StatusCode == 302 {
				mu.Lock()
				results = append(results, OdintManagementUI{Type: uiType, URL: baseURL + path, StatusCode: resp.StatusCode})
				mu.Unlock()
			}
		}(p.path, p.uiType)
	}
	wg.Wait()
	return results
}

func (tui *TUI) odintStoreManagementUIs(domain string, uis []OdintManagementUI) {
	for _, u := range uis {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_management_uis
			(domain, ui_type, url, status_code, discovered) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, u.Type, u.URL, u.StatusCode)
	}
}

// ===========================================================================
// GAP FILL: AMP DETECTION
// ===========================================================================

func odintDetectAMP(ctx context.Context, client *http.Client, baseURL, body string) *OdintAMPInfo {
	info := &OdintAMPInfo{}

	htmlAmpRe := regexp.MustCompile(`(?i)<html[^>]*(amp|⚡)[^>]*>`)
	if htmlAmpRe.MatchString(body) {
		info.HasAMP = true
	}

	ampLinkRe := regexp.MustCompile(`(?i)<link[^>]*rel=["']amphtml["'][^>]*href=["']([^"']+)["']`)
	if m := ampLinkRe.FindStringSubmatch(body); len(m) > 1 {
		info.HasAMP = true
		info.AMPURL = m[1]
	}

	if !info.HasAMP {
		req, err := http.NewRequestWithContext(ctx, "HEAD", baseURL+"/amp/", nil)
		if err == nil {
			req.Header.Set("User-Agent", UserAgent)
			resp, err := client.Do(req)
			if err == nil {
				resp.Body.Close()
				if resp.StatusCode == 200 {
					info.HasAMP = true
					info.AMPURL = baseURL + "/amp/"
				}
			}
		}
	}

	if !info.HasAMP {
		return nil
	}

	ampElemRe := regexp.MustCompile(`<(amp-[a-z-]+)`)
	seen := make(map[string]bool)
	for _, m := range ampElemRe.FindAllStringSubmatch(body, 50) {
		if len(m) > 1 && !seen[m[1]] {
			seen[m[1]] = true
			info.AMPElements = append(info.AMPElements, m[1])
		}
	}

	ampAnalyticsRe := regexp.MustCompile(`(?i)<amp-analytics[^>]*type=["']([^"']+)["']`)
	for _, m := range ampAnalyticsRe.FindAllStringSubmatch(body, 10) {
		if len(m) > 1 {
			info.AMPAnalytics = append(info.AMPAnalytics, m[1])
		}
	}

	info.CacheURL = fmt.Sprintf("https://cdn.ampproject.org/c/s/%s",
		strings.TrimPrefix(strings.TrimPrefix(baseURL, "https://"), "http://"))

	return info
}

// ===========================================================================
// GAP FILL: URL PATTERN ANALYSIS
// ===========================================================================

func odintAnalyzeURLPatterns(body, baseURL string) *OdintURLPatternInfo {
	info := &OdintURLPatternInfo{}

	// Pagination
	pageRe := regexp.MustCompile(`(?i)(?:page[=/]|[?&]page=|[?&]p=|[?&]offset=)(\d+)`)
	maxPage := 0
	for _, m := range pageRe.FindAllStringSubmatch(body, 100) {
		if len(m) > 1 {
			if n, err := strconv.Atoi(m[1]); err == nil && n > maxPage {
				maxPage = n
			}
		}
	}
	info.PaginationMax = maxPage

	// Sequential IDs
	seqRe := regexp.MustCompile(`(?i)href=["'](?:[^"']*/)(\\d{1,8})(?:/|["']|$)`)
	seenIDs := make(map[string]bool)
	for _, m := range seqRe.FindAllStringSubmatch(body, 50) {
		if len(m) > 1 && !seenIDs[m[1]] {
			seenIDs[m[1]] = true
			info.SequentialIDs = append(info.SequentialIDs, m[1])
			if len(info.SequentialIDs) >= 20 {
				break
			}
		}
	}

	// Locale patterns
	localeRe := regexp.MustCompile(`(?i)href=["'][^"']*/([a-z]{2}(?:-[A-Z]{2})?)/[^"']*["']`)
	locales := map[string]bool{
		"en": true, "fr": true, "de": true, "es": true, "pt": true, "it": true, "nl": true,
		"ru": true, "ja": true, "ko": true, "zh": true, "ar": true, "hi": true, "pl": true,
		"sv": true, "da": true, "no": true, "fi": true, "cs": true, "tr": true, "th": true,
	}
	seenLoc := make(map[string]bool)
	for _, m := range localeRe.FindAllStringSubmatch(body, 50) {
		if len(m) > 1 && locales[strings.ToLower(m[1])] && !seenLoc[m[1]] {
			seenLoc[m[1]] = true
			info.LocalePatterns = append(info.LocalePatterns, m[1])
		}
	}

	// Date URLs
	dateRe := regexp.MustCompile(`href=["'][^"']*(/\d{4}/\d{2}(?:/\d{2})?/)[^"']*["']`)
	seenDates := make(map[string]bool)
	for _, m := range dateRe.FindAllStringSubmatch(body, 20) {
		if len(m) > 1 && !seenDates[m[1]] {
			seenDates[m[1]] = true
			info.DateURLs = append(info.DateURLs, m[1])
		}
	}

	return info
}

// ===========================================================================
// GAP FILL: TIMEZONE / LOCALE EXTRACTION
// ===========================================================================

func odintExtractTimezoneLocale(headers http.Header, body string) *OdintTimezoneLocaleInfo {
	info := &OdintTimezoneLocaleInfo{}

	// Server timezone from Date header
	if dh := headers.Get("Date"); dh != "" {
		if strings.Contains(dh, "GMT") {
			info.ServerTimezone = "GMT"
		} else if strings.Contains(dh, "UTC") {
			info.ServerTimezone = "UTC"
		}
	}

	// JS timezone references
	tzRe := regexp.MustCompile(`(?i)(?:timezone|tz|time_zone)['":]+\s*["']([A-Za-z]+/[A-Za-z_]+)["']`)
	seen := make(map[string]bool)
	for _, m := range tzRe.FindAllStringSubmatch(body, 10) {
		if len(m) > 1 && !seen[m[1]] {
			seen[m[1]] = true
			info.JSTimezones = append(info.JSTimezones, m[1])
		}
	}

	// Date formats
	dateFormats := []struct{ name, pattern string }{
		{"YYYY-MM-DD", `\d{4}-\d{2}-\d{2}`},
		{"DD.MM.YYYY", `\d{2}\.\d{2}\.\d{4}`},
		{"DD/MM/YYYY", `\d{2}/\d{2}/\d{4}`},
	}
	for _, df := range dateFormats {
		if regexp.MustCompile(df.pattern).MatchString(body) {
			info.DateFormats = append(info.DateFormats, df.name)
		}
	}

	// Locale from HTML lang
	langRe := regexp.MustCompile(`(?i)<html[^>]*\slang=["']([^"']+)["']`)
	if m := langRe.FindStringSubmatch(body); len(m) > 1 {
		info.Locale = m[1]
	}
	if info.Locale == "" {
		info.Locale = headers.Get("Content-Language")
	}

	return info
}

// ===========================================================================
// GAP FILL: ENHANCED ROBOTS.TXT ANALYSIS
// ===========================================================================

func odintEnhancedRobotsTxt(ctx context.Context, client *http.Client, baseURL string) *OdintRobotsTxtIntel {
	req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/robots.txt", nil)
	if err != nil {
		return nil
	}
	req.Header.Set("User-Agent", UserAgent)
	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil
	}
	bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 100*1024))
	body := string(bodyBytes)

	intel := &OdintRobotsTxtIntel{}
	currentUA := "*"
	interestingPaths := []string{
		"admin", "login", "api", "internal", "staging", "debug", "backup",
		"private", "secret", "config", "dashboard", "panel", "manage",
	}

	for _, line := range strings.Split(body, "\n") {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.SplitN(line, ":", 2)
		if len(parts) != 2 {
			continue
		}
		key := strings.TrimSpace(strings.ToLower(parts[0]))
		val := strings.TrimSpace(parts[1])

		switch key {
		case "user-agent":
			currentUA = val
			intel.UserAgent = val
		case "disallow":
			intel.Disallows = append(intel.Disallows, val)
			for _, p := range interestingPaths {
				if strings.Contains(strings.ToLower(val), p) {
					intel.HiddenPaths = append(intel.HiddenPaths, val)
					break
				}
			}
		case "allow":
			intel.Allows = append(intel.Allows, val)
		case "crawl-delay":
			intel.CrawlDelay = val
		}
	}
	_ = currentUA

	return intel
}

// ===========================================================================
// GAP FILL: STRUCTURED DATA — Microdata, RDFa, Dublin Core
// ===========================================================================

func odintExtractStructuredDataFull(body string) []OdintStructuredDataItem {
	var results []OdintStructuredDataItem

	// Microdata: itemtype
	itemTypeRe := regexp.MustCompile(`(?i)itemtype=["']([^"']+)["']`)
	for _, m := range itemTypeRe.FindAllStringSubmatch(body, 20) {
		if len(m) > 1 {
			results = append(results, OdintStructuredDataItem{
				Type: "microdata", ItemType: m[1],
				Properties: map[string]string{"itemtype": m[1]},
			})
		}
	}

	// Microdata: itemprop with content
	itemPropRe := regexp.MustCompile(`(?i)itemprop=["']([^"']+)["'][^>]*content=["']([^"']+)["']`)
	propMap := make(map[string]string)
	for _, m := range itemPropRe.FindAllStringSubmatch(body, 50) {
		if len(m) > 2 {
			propMap[m[1]] = m[2]
		}
	}
	if len(propMap) > 0 {
		results = append(results, OdintStructuredDataItem{Type: "microdata", ItemType: "properties", Properties: propMap})
	}

	// RDFa: typeof
	rdfaTypeRe := regexp.MustCompile(`(?i)typeof=["']([^"']+)["']`)
	for _, m := range rdfaTypeRe.FindAllStringSubmatch(body, 20) {
		if len(m) > 1 {
			results = append(results, OdintStructuredDataItem{
				Type: "rdfa", ItemType: m[1],
				Properties: map[string]string{"typeof": m[1]},
			})
		}
	}

	// Dublin Core
	dcRe := regexp.MustCompile(`(?i)<meta\s+name=["'](DC\.[^"']+)["']\s+content=["']([^"']+)["']`)
	dcProps := make(map[string]string)
	for _, m := range dcRe.FindAllStringSubmatch(body, 20) {
		if len(m) > 2 {
			dcProps[m[1]] = m[2]
		}
	}
	if len(dcProps) > 0 {
		results = append(results, OdintStructuredDataItem{Type: "dublin_core", ItemType: "Dublin Core Metadata", Properties: dcProps})
	}

	return results
}

func (tui *TUI) odintStoreStructuredData(domain string, items []OdintStructuredDataItem) {
	for _, item := range items {
		propsJSON, _ := json.Marshal(item.Properties)
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_structured_data
			(domain, data_type, item_type, properties, discovered) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, item.Type, item.ItemType, string(propsJSON))
	}
}

// ===========================================================================
// GAP FILL: HISTORICAL — Archive.today & Common Crawl
// ===========================================================================

func odintFetchArchiveToday(ctx context.Context, client *http.Client, domain string) []OdintHistoricalRecord {
	var results []OdintHistoricalRecord
	apiURL := fmt.Sprintf("https://archive.ph/newest/%s", domain)
	req, err := http.NewRequestWithContext(ctx, "HEAD", apiURL, nil)
	if err != nil {
		return nil
	}
	req.Header.Set("User-Agent", UserAgent)
	resp, err := client.Do(req)
	if err != nil {
		return nil
	}
	resp.Body.Close()
	if resp.StatusCode == 200 || resp.StatusCode == 302 {
		loc := resp.Header.Get("Location")
		if loc == "" {
			loc = apiURL
		}
		results = append(results, OdintHistoricalRecord{
			Source: "archive.today", Type: "snapshot", Data: loc, Date: time.Now().Format("2006-01-02"),
		})
	}
	return results
}

func odintFetchCommonCrawl(ctx context.Context, client *http.Client, domain string) []OdintHistoricalRecord {
	var results []OdintHistoricalRecord
	collections := []string{"CC-MAIN-2025-13", "CC-MAIN-2024-51", "CC-MAIN-2024-33"}

	for _, coll := range collections {
		if ctx.Err() != nil {
			break
		}
		apiURL := fmt.Sprintf("https://index.commoncrawl.org/%s-index?url=%s&output=json&limit=5",
			coll, url.QueryEscape("*."+domain))
		reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
		req, err := http.NewRequestWithContext(reqCtx, "GET", apiURL, nil)
		if err != nil {
			cancel()
			continue
		}
		req.Header.Set("User-Agent", UserAgent)
		resp, err := client.Do(req)
		if err != nil {
			cancel()
			continue
		}
		bodyBytes, _ := io.ReadAll(io.LimitReader(resp.Body, 500*1024))
		resp.Body.Close()
		cancel()

		for _, line := range strings.Split(string(bodyBytes), "\n") {
			line = strings.TrimSpace(line)
			if line == "" {
				continue
			}
			var rec struct {
				URL       string `json:"url"`
				Timestamp string `json:"timestamp"`
				Status    string `json:"status"`
			}
			if json.Unmarshal([]byte(line), &rec) == nil && rec.URL != "" {
				results = append(results, OdintHistoricalRecord{
					Source: "commoncrawl", Type: coll, Data: rec.URL, Date: rec.Timestamp,
				})
			}
		}
	}
	return results
}

func (tui *TUI) odintStoreHistoricalRecords(domain string, records []OdintHistoricalRecord) {
	for _, r := range records {
		tui.db.conn.Exec(`INSERT OR IGNORE INTO odint_historical_records
			(domain, source, record_type, data, record_date, discovered) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
			domain, r.Source, r.Type, r.Data, r.Date)
	}
}

// ===========================================================================
// GAP FILL: DNS — DKIM selector check & SRV lookups
// ===========================================================================

func odintLookupDKIM(ctx context.Context, domain string) []string {
	var results []string
	selectors := []string{"default", "google", "selector1", "selector2", "k1", "mail", "dkim", "s1", "s2"}
	resolver := &net.Resolver{}

	for _, sel := range selectors {
		if ctx.Err() != nil {
			break
		}
		lookupCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
		fqdn := sel + "._domainkey." + domain
		txts, err := resolver.LookupTXT(lookupCtx, fqdn)
		cancel()
		if err == nil {
			for _, txt := range txts {
				if strings.Contains(txt, "v=DKIM") || strings.Contains(txt, "p=") {
					results = append(results, fmt.Sprintf("%s: %s", fqdn, txt))
				}
			}
		}
	}
	return results
}

func odintLookupSRV(ctx context.Context, domain string) []string {
	var results []string
	services := []struct{ service, proto string }{
		{"http", "tcp"}, {"https", "tcp"}, {"sip", "tcp"}, {"sip", "udp"},
		{"xmpp-server", "tcp"}, {"xmpp-client", "tcp"}, {"imap", "tcp"},
		{"imaps", "tcp"}, {"pop3", "tcp"}, {"pop3s", "tcp"}, {"smtp", "tcp"},
		{"submission", "tcp"}, {"ldap", "tcp"}, {"kerberos", "tcp"},
	}

	for _, svc := range services {
		if ctx.Err() != nil {
			break
		}
		lookupCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
		_, addrs, err := net.DefaultResolver.LookupSRV(lookupCtx, svc.service, svc.proto, domain)
		cancel()
		if err == nil && len(addrs) > 0 {
			for _, a := range addrs {
				results = append(results, fmt.Sprintf("_%s._%s.%s -> %s:%d (pri=%d w=%d)",
					svc.service, svc.proto, domain, a.Target, a.Port, a.Priority, a.Weight))
			}
		}
	}
	return results
}

// ===========================================================================
// GAP FILL: CREDENTIAL SCANNER — 521 paths with 4-stage validation
// ===========================================================================

var odintCredentialPaths = []string{
	// Environment files
	"/.env", "/.env.local", "/.env.development", "/.env.production", "/.env.staging",
	"/.env.test", "/.env.backup", "/.env.bak", "/.env.old", "/.env.save",
	"/.env.dev", "/.env.prod", "/.env.example", "/.env.sample", "/.env.dist",
	"/.env.docker", "/.env.swp", "/.env~", "/env.js", "/env.json", "/env.yaml", "/env.yml",
	"/config/.env", "/app/.env", "/src/.env",
	// Git / VCS
	"/.git/config", "/.git/HEAD", "/.git/index", "/.git/logs/HEAD", "/.git/refs/heads/main",
	"/.git/refs/heads/master", "/.git/COMMIT_EDITMSG", "/.git/description",
	"/.gitconfig", "/.svn/entries", "/.svn/wc.db", "/.hg/hgrc", "/.bzr/README",
	// AWS
	"/.aws/credentials", "/.aws/config", "/.boto", "/.s3cfg", "/aws-credentials.json",
	// GCP
	"/.config/gcloud/credentials.db", "/.config/gcloud/application_default_credentials.json",
	"/gcloud-service-key.json", "/service-account.json", "/service-account-key.json",
	"/google-services.json", "/google-credentials.json", "/firebase-adminsdk.json",
	// Azure
	"/.azure/credentials", "/.azure/accessTokens.json",
	// SSH / Keys
	"/.ssh/id_rsa", "/.ssh/id_rsa.pub", "/.ssh/id_ed25519", "/.ssh/id_dsa",
	"/.ssh/authorized_keys", "/.ssh/known_hosts", "/.ssh/config",
	"/server.key", "/server.pem", "/privkey.pem", "/cert.pem", "/fullchain.pem",
	"/private.key", "/private.pem", "/ssl.key", "/ssl.pem", "/tls.key", "/tls.pem",
	"/certificate.pem", "/ca-bundle.crt", "/ca.pem",
	// JWT / Signing
	"/jwt.key", "/jwt-key.pem", "/jwtRS256.key", "/jwtES256.key", "/signing.key",
	"/oauth-private.key", "/oauth-public.key", "/saml.pem", "/saml.key", "/oidc.key",
	// Credential / Secret files
	"/credentials.json", "/credentials.yaml", "/credentials.yml", "/credentials.xml",
	"/secrets.json", "/secrets.yaml", "/secrets.yml", "/secrets.env",
	"/passwords.txt", "/passwords.json", "/passwords.csv",
	"/.htpasswd", "/htpasswd", "/passwd", "/shadow",
	"/.netrc", "/.dockercfg", "/.docker/config.json",
	"/vault.json", "/vault.yaml", "/master.key",
	"/keystore.jks", "/truststore.jks", "/keystore.p12",
	// Config files with secrets
	"/wp-config.php", "/wp-config.php.bak", "/wp-config.php.old",
	"/config.php", "/configuration.php", "/settings.php", "/local.settings.php",
	"/config.yml", "/config.yaml", "/config.json", "/config.xml", "/config.ini",
	"/application.yml", "/application.yaml", "/application.properties",
	"/appsettings.json", "/appsettings.Development.json", "/appsettings.Production.json",
	"/database.yml", "/database.yaml", "/database.json",
	"/docker-compose.yml", "/docker-compose.yaml", "/docker-compose.override.yml",
	"/Dockerfile", "/.dockerignore",
	// Terraform / IaC
	"/terraform.tfstate", "/terraform.tfstate.backup", "/terraform.tfvars",
	"/.terraform/", "/terraform.auto.tfvars", "/main.tf", "/variables.tf",
	"/outputs.tf", "/backend.tf", "/providers.tf",
	// Ansible
	"/ansible.cfg", "/inventory", "/group_vars/all.yml", "/host_vars/",
	"/vault.yml", "/vault-password.txt",
	// Kubernetes
	"/kubeconfig", "/.kube/config", "/kube-config.yaml",
	"/kubernetes-secret.yaml", "/k8s-secret.yaml",
	// CI/CD
	"/.github/workflows/", "/.gitlab-ci.yml", "/.circleci/config.yml",
	"/Jenkinsfile", "/jenkins.yaml", "/.drone.yml",
	"/azure-pipelines.yml", "/bitbucket-pipelines.yml",
	"/.buildkite/pipeline.yml", "/buildspec.yml",
	"/.travis.yml", "/appveyor.yml", "/.semaphore/semaphore.yml",
	"/netlify.toml", "/vercel.json", "/fly.toml", "/render.yaml",
	"/railway.json", "/Procfile", "/heroku.yml", "/serverless.yml",
	// IDE
	"/.vscode/settings.json", "/.vscode/launch.json", "/.vscode/tasks.json",
	"/.idea/workspace.xml", "/.idea/deployment.xml", "/.idea/dataSources.xml",
	"/.idea/dbnavigator.xml", "/.idea/webServers.xml",
	"/.project", "/.classpath", "/.settings/org.eclipse.wst.common.project.facet.core.xml",
	// Package managers
	"/.npmrc", "/.yarnrc", "/.yarnrc.yml", "/.pip/pip.conf", "/.pypirc",
	"/.gemrc", "/.bundler/config", "/.m2/settings.xml", "/nuget.config",
	// Database configs
	"/.pgpass", "/.my.cnf", "/mongodb.conf", "/redis.conf", "/redis.yaml",
	"/mongod.conf", "/pg_hba.conf", "/my.ini",
	// Monitoring
	"/newrelic.yml", "/newrelic.js", "/datadog.yaml", "/sentry.properties",
	"/.sentryclirc", "/bugsnag.json", "/rollbar.json", "/elastic.yml",
	"/logstash.conf", "/filebeat.yml",
	// Messaging
	"/mailgun.json", "/sendgrid.json", "/smtp-config.json", "/twilio.json",
	// Crypto
	"/hardhat.config.js", "/truffle-config.js", "/foundry.toml",
	"/.secret", "/mnemonic.txt", "/wallet.json",
	// Docker runtime
	"/run/secrets/", "/run/secrets/db_password",
	// Shell history
	"/.bash_history", "/.zsh_history", "/.mysql_history", "/.psql_history",
	"/.python_history", "/.node_repl_history",
	// AI coding tools
	"/.cursorrules", "/.cursor/settings.json", "/.cursor/mcp.json",
	"/CLAUDE.md", "/.claude/settings.json", "/.anthropic/config.json",
	"/AGENTS.md", "/.bolt/config.json", "/.lovable/config.json",
	"/.windsurf/", "/.windsurfrules", "/.copilot/config",
	"/mcp.json", "/.mcp/config.json", "/mcp-config.json",
	"/.replit", "/replit.nix",
	// Vibe coder hardcoded secrets
	"/constants.js", "/constants.ts", "/src/constants.js", "/src/constants.ts",
	"/src/config.js", "/src/config.ts", "/src/config/index.js",
	"/lib/config.js", "/lib/config.ts", "/utils/config.js",
	// Firebase / Supabase
	"/firebase.config.js", "/firebase.config.ts", "/.firebaserc", "/firebase.json",
	// Doppler / Infisical
	"/.doppler/config.json", "/.infisical.json",
	// Elixir
	"/prod.secret.exs", "/config.exs", "/dev.exs",
	// Backups
	"/backup.sql", "/dump.sql", "/database.sql", "/db.sql", "/data.sql",
	"/backup.sql.gz", "/www.zip", "/www.tar.gz", "/web.zip", "/site.zip",
	"/src.zip", "/source.zip", "/backup.tar.gz", "/backup.zip",
	// Debug / Logs
	"/debug/pprof/", "/debug.log", "/error.log", "/error_log", "/access.log",
	"/app.log", "/application.log", "/server.log", "/npm-debug.log",
	"/yarn-error.log", "/crash.log",
	// Admin DB tools
	"/adminer.php", "/phpmyadmin/", "/pma/", "/dbadmin/", "/pgadmin/",
	"/mongo-express/", "/redis-commander",
}

var odintCredentialKeywords = []string{
	"DB_PASSWORD", "DB_USERNAME", "DATABASE_URL", "DATABASE_PASSWORD",
	"MYSQL_PASSWORD", "MYSQL_ROOT_PASSWORD", "POSTGRES_PASSWORD", "PGPASSWORD",
	"MONGO_URI", "MONGODB_URI", "REDIS_URL", "REDIS_PASSWORD",
	"SUPABASE_URL", "SUPABASE_KEY", "SUPABASE_SERVICE_ROLE_KEY",
	"FIREBASE_API_KEY", "FIREBASE_PRIVATE_KEY",
	"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN",
	"GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_PRIVATE_KEY", "GOOGLE_CLIENT_SECRET",
	"AZURE_CLIENT_SECRET", "AZURE_STORAGE_KEY",
	"API_KEY", "API_SECRET", "API_TOKEN", "SECRET_KEY", "PRIVATE_KEY",
	"AUTH_TOKEN", "BEARER_TOKEN", "JWT_SECRET", "JWT_PRIVATE_KEY",
	"SESSION_SECRET", "COOKIE_SECRET", "ENCRYPTION_KEY", "MASTER_KEY",
	"STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET", "PAYPAL_CLIENT_SECRET",
	"SENDGRID_API_KEY", "MAILGUN_API_KEY", "TWILIO_AUTH_TOKEN",
	"CLERK_SECRET_KEY", "AUTH0_SECRET", "NEXTAUTH_SECRET",
	"GITHUB_TOKEN", "GITLAB_TOKEN", "BITBUCKET_SECRET",
	"NPM_TOKEN", "DOCKER_PASSWORD",
	"SLACK_TOKEN", "SLACK_WEBHOOK_URL", "TELEGRAM_BOT_TOKEN",
	"CLOUDFLARE_API_KEY", "CLOUDFLARE_API_TOKEN",
	"OPENAI_API_KEY", "ANTHROPIC_API_KEY",
	"SENTRY_DSN", "DATADOG_API_KEY", "NEW_RELIC_LICENSE_KEY",
	"VERCEL_TOKEN", "HEROKU_API_KEY",
	"ALCHEMY_API_KEY", "WALLET_PRIVATE_KEY", "MNEMONIC",
	"DJANGO_SECRET_KEY", "FLASK_SECRET_KEY", "RAILS_MASTER_KEY", "SECRET_KEY_BASE",
}

var odintEnvKeyValueRe = regexp.MustCompile(`(?m)^[A-Z_][A-Z0-9_]*=.+$`)
var odintGitConfigRe = regexp.MustCompile(`(?m)^\[(core|remote|branch|user)(\s|])`)
var odintConnStringRe = regexp.MustCompile(`(?i)(mongodb|postgres(?:ql)?|mysql|redis|amqp)(?:\+srv)?://[^\s'"<>]+`)
var odintPrivKeyRe = regexp.MustCompile(`-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----`)

func odintScanCredentials(ctx context.Context, client *http.Client, baseURL string) []OdintSecretFinding {
	// Get baseline for soft 404 detection
	baselineLen := 0
	bReq, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/nonexistent-baseline-404-check", nil)
	if err == nil {
		bReq.Header.Set("User-Agent", UserAgent)
		bResp, err := client.Do(bReq)
		if err == nil {
			bBody, _ := io.ReadAll(io.LimitReader(bResp.Body, 50*1024))
			bResp.Body.Close()
			baselineLen = len(bBody)
		}
	}

	var results []OdintSecretFinding
	var mu sync.Mutex
	sem := make(chan struct{}, 20)
	var wg sync.WaitGroup

	for _, path := range odintCredentialPaths {
		if ctx.Err() != nil {
			break
		}
		wg.Add(1)
		sem <- struct{}{}
		go func(path string) {
			defer wg.Done()
			defer func() { <-sem }()

			req, err := http.NewRequestWithContext(ctx, "GET", baseURL+path, nil)
			if err != nil {
				return
			}
			req.Header.Set("User-Agent", UserAgent)
			resp, err := client.Do(req)
			if err != nil || resp.StatusCode != 200 {
				if resp != nil {
					resp.Body.Close()
				}
				return
			}
			body, _ := io.ReadAll(io.LimitReader(resp.Body, 50*1024))
			resp.Body.Close()
			if len(body) == 0 {
				return
			}

			ct := strings.ToLower(resp.Header.Get("Content-Type"))
			bodyStr := string(body)

			// Stage 1: Reject HTML content-type
			if strings.Contains(ct, "text/html") {
				return
			}

			// Stage 2: Reject HTML indicators
			lower := bodyStr
			if len(lower) > 500 {
				lower = lower[:500]
			}
			lower = strings.ToLower(lower)
			for _, ind := range []string{"<!doctype", "<html", "<head>", "<body", "<meta", "<script"} {
				if strings.Contains(lower, ind) {
					return
				}
			}

			// Stage 3: Soft 404 detection
			if baselineLen > 0 {
				diff := float64(len(body)) - float64(baselineLen)
				if diff < 0 {
					diff = -diff
				}
				if diff <= float64(baselineLen)*0.05 {
					return
				}
			}

			// Stage 4: Pattern matching
			var matched []string
			pathLower := strings.ToLower(path)

			if strings.Contains(pathLower, ".env") {
				if len(odintEnvKeyValueRe.FindAllString(bodyStr, -1)) >= 3 {
					matched = append(matched, "env_file")
				}
			}
			if strings.Contains(pathLower, ".git") {
				if odintGitConfigRe.MatchString(bodyStr) {
					matched = append(matched, "git_config")
				}
			}
			if odintPrivKeyRe.MatchString(bodyStr) {
				matched = append(matched, "private_key")
			}
			if odintConnStringRe.MatchString(bodyStr) {
				matched = append(matched, "connection_string")
			}

			bodyUpper := strings.ToUpper(bodyStr)
			for _, kw := range odintCredentialKeywords {
				if strings.Contains(bodyUpper, kw) {
					matched = append(matched, kw)
					if len(matched) >= 10 {
						break
					}
				}
			}

			if len(matched) == 0 {
				return
			}

			// Classify
			credType := "exposed_credential_file"
			if strings.Contains(pathLower, ".env") {
				credType = "exposed_env_file"
			} else if strings.Contains(pathLower, ".git") {
				credType = "exposed_git_config"
			} else if strings.Contains(pathLower, "terraform") {
				credType = "exposed_terraform"
			} else if strings.Contains(pathLower, "docker") || strings.Contains(pathLower, "kube") {
				credType = "exposed_container_config"
			} else if strings.Contains(pathLower, "key") || strings.Contains(pathLower, ".pem") {
				credType = "exposed_private_key"
			} else if strings.Contains(pathLower, ".sql") || strings.Contains(pathLower, "dump") {
				credType = "exposed_database_dump"
			}

			severity := "medium"
			for _, p := range []string{".env", "credentials", "secret", "private", "key", ".pem", "id_rsa", "passwd", "shadow", "htpasswd", "terraform.tfstate", "master.key", "vault", ".aws/", ".ssh/", "wallet", "mnemonic"} {
				if strings.Contains(pathLower, p) {
					severity = "critical"
					break
				}
			}
			if severity != "critical" {
				for _, p := range []string{"config", "settings", "database", "docker", "kube", ".git/", "jenkins", "ci.yml", "deploy"} {
					if strings.Contains(pathLower, p) {
						severity = "high"
						break
					}
				}
			}

			displayVal := bodyStr
			if len(displayVal) > 200 {
				displayVal = displayVal[:200] + "..."
			}

			mu.Lock()
			results = append(results, OdintSecretFinding{
				Type: credType, Value: displayVal, Location: path,
				Severity: severity, Context: strings.Join(matched, ", "),
			})
			mu.Unlock()
		}(path)
	}
	wg.Wait()
	return results
}

// ===========================================================================
// COMPILE GUARDS — ensure all ODINT imports are used
// ===========================================================================

var _ = url.QueryEscape
var _ = sort.Strings
var _ = tls.VersionTLS13
var _ = xml.Unmarshal
var _ = exec.LookPath
var _ = runtime.GOOS
var _ = sync.Mutex{}
var _ = base64.URLEncoding
var _ = hex.EncodeToString
var _ = md5.New
var _ = sha256.New
var _ = bytes.NewReader
var _ = bufio.NewScanner
var _ = math.Max
