#include "html.h"
#include "http.h"
#include "util.h"
#include "printf.h"

/* Output buffer — defined in http.c, used by cmd_less() */
extern char html_buf[];
extern uint32_t html_len;

#define OUT_SIZE (sizeof(html_buf) - 1)
static uint32_t out_pos;

/* Link tracking */
struct html_link html_links[HTML_MAX_LINKS];
int html_link_count;
char html_base_url[HTML_URL_SIZE];

/* Current output position tracking */
static int out_line;
static int out_col;

/* Active link being rendered */
static int active_link_idx = -1;  /* -1 = not in a link */

static void out_char(char c) {
    if(out_pos < OUT_SIZE) { html_buf[out_pos++] = c; }
    if(c == '\n') { out_line++; out_col = 0; }
    else { out_col++; }
}

static void out_codepoint(uint32_t cp);

/* ── URL resolution ── */
static void resolve_url(const char *base, const char *rel, char *out, int outsize) {
    /* Absolute URL */
    if(strncmp(rel, "http://", 7) == 0 || strncmp(rel, "https://", 8) == 0) {
        int len = strlen(rel);
        if(len >= outsize) len = outsize - 1;
        memcpy(out, rel, len);
        out[len] = '\0';
        return;
    }
    /* Start from base: find scheme + authority */
    int copylen = 0;
    if(strncmp(base, "https://", 8) == 0) copylen = 8;
    else if(strncmp(base, "http://", 7) == 0) copylen = 7;
    /* Copy up to first '/' after authority */
    const char *p = base + copylen;
    while(*p && *p != '/') p++;
    int authority_end = (int)(p - base);
    if(authority_end >= outsize) authority_end = outsize - 1;
    memcpy(out, base, authority_end);
    int pos = authority_end;

    if(rel[0] == '/') {
        /* Root-relative */
        int rlen = strlen(rel);
        if(pos + rlen >= outsize) rlen = outsize - pos - 1;
        memcpy(out + pos, rel, rlen);
        pos += rlen;
    } else {
        /* Path-relative: replace last segment */
        int last_slash = authority_end;
        for(int i = authority_end; i < pos; i++)
            if(out[i] == '/') last_slash = i;
        pos = last_slash + 1;
        int rlen = strlen(rel);
        if(pos + rlen >= outsize) rlen = outsize - pos - 1;
        memcpy(out + pos, rel, rlen);
        pos += rlen;
    }
    out[pos] = '\0';
}

/* ── Start a link ── */
static void begin_link(const char *href) {
    if(html_link_count >= HTML_MAX_LINKS) return;
    struct html_link *lk = &html_links[html_link_count];
    lk->line = out_line;
    lk->col = out_col;
    lk->len = 0;
    resolve_url(html_base_url, href, lk->url, HTML_URL_SIZE);
    active_link_idx = html_link_count;
    html_link_count++;
}

/* ── End a link ── */
static void end_link(void) {
    if(active_link_idx >= 0 && active_link_idx < html_link_count) {
        html_links[active_link_idx].len = out_col - html_links[active_link_idx].col;
        /* Handle multi-line link: approximate line length */
        if(html_links[active_link_idx].len <= 0)
            html_links[active_link_idx].len = 1;
    }
    active_link_idx = -1;
}

static void out_str(const char *s) {
    while(*s) {
        unsigned char c = (unsigned char)*s;
        if(c >= 0xC0) {
            /* UTF-8 decode */
            uint32_t cp = 0;
            int extra = 0;
            if((c & 0xE0) == 0xC0) { cp = c & 0x1F; extra = 1; }
            else if((c & 0xF0) == 0xE0) { cp = c & 0x0F; extra = 2; }
            else if((c & 0xF8) == 0xF0) { cp = c & 0x07; extra = 3; }
            s++;
            for(int i = 0; i < extra; i++) {
                if(((unsigned char)*s & 0xC0) == 0x80)
                    cp = (cp << 6) | ((unsigned char)*s++ & 0x3F);
                else break;
            }
            out_codepoint(cp);
        } else {
            out_char(*s++);
        }
    }
}

static void out_nls(int n) {
    for(int i = 0; i < n; i++) out_char('\n');
}

static void out_indent(int depth) {
    for(int i = 0; i < depth && i < 8; i++) out_str("  ");
}

/* ── Case-insensitive tag name comparison ── */
static int tag_is(const char *html, const char *name) {
    const char *h = html;
    const char *n = name;
    while(*n) {
        char a = *h >= 'A' && *h <= 'Z' ? *h + 32 : *h;
        char b = *n;
        if(a != b) return 0;
        h++; n++;
    }
    /* Must be followed by non-alnum (end of tag name) */
    if(*h >= 'a' && *h <= 'z') return 0;
    if(*h >= 'A' && *h <= 'Z') return 0;
    if(*h >= '0' && *h <= '9') return 0;
    return 1;
}

/* ── HTML entity decoding ── */
static const struct { const char *ent; int ch; } entities[] = {
    {"amp", '&'}, {"lt", '<'}, {"gt", '>'},
    {"nbsp", ' '}, {"quot", '"'}, {"apos", '\''},
    {"mdash", 0x97}, {"ndash", 0x96}, {"hellip", 0x85},
    {"laquo", 0xAB}, {"raquo", 0xBB}, {"copy", 0xA9},
    {"reg", 0xAE}, {"trade", 0x99}, {"times", 0xD7},
    {"divide", 0xF7}, {"plus", '+'}, {"minus", '-'},
    {"lsquo", 0x91}, {"rsquo", 0x92}, {"ldquo", 0x93}, {"rdquo", 0x94},
    {"bull", 0x95},
    {0, 0}
};

static int decode_entity(const char *p) {
    if(*p == '#') {
        p++;
        int val = 0;
        if(*p == 'x' || *p == 'X') {
            p++;
            while(*p && *p != ';') {
                val <<= 4;
                if(*p >= '0' && *p <= '9') val |= *p - '0';
                else if(*p >= 'a' && *p <= 'f') val |= *p - 'a' + 10;
                else if(*p >= 'A' && *p <= 'F') val |= *p - 'A' + 10;
                p++;
            }
        } else {
            while(*p && *p != ';') {
                val = val * 10 + (*p - '0');
                p++;
            }
        }
        return val;
    }
    for(int i = 0; entities[i].ent; i++) {
        if(strncmp(p, entities[i].ent, strlen(entities[i].ent)) == 0)
            return entities[i].ch;
    }
    return -1;
}

/* ── Read an attribute value (quoted or unquoted) ── */
static const char *read_attr_value(const char *p, char *buf, int bufsize) {
    int i = 0;
    if(*p == '"' || *p == '\'') {
        char q = *p++;
        while(*p && *p != q && i < bufsize - 1) buf[i++] = *p++;
        if(*p == q) p++;
    } else {
        while(*p && *p != ' ' && *p != '>' && *p != '/' &&
              *p != '\t' && *p != '\n' && *p != '\r' && i < bufsize - 1)
            buf[i++] = *p++;
    }
    buf[i] = 0;
    return p;
}

/* ── Check if a tag name is in the skip list ── */
static int is_skip_tag(const char *name) {
    /* Tags whose content is completely skipped */
    static const char *skip[] = {
        "script", "style", "head", "noscript", "template", 0
    };
    for(int i = 0; skip[i]; i++) {
        if(tag_is(name, skip[i])) return 1;
    }
    return 0;
}

static int is_svg_tag(const char *name) {
    return tag_is(name, "svg");
}

/* Self-closing tags */
static int is_self_closing(const char *name) {
    static const char *sc[] = {
        "br", "hr", "img", "input", "meta", "link",
        "area", "base", "col", "embed", "source", "track", "wbr", 0
    };
    for(int i = 0; sc[i]; i++) {
        if(tag_is(name, sc[i])) return 1;
    }
    return 0;
}

/* Block-level tags that start on a new line */
static int is_block(const char *name) {
    static const char *bl[] = {
        "p", "div", "h1", "h2", "h3", "h4", "h5", "h6",
        "li", "ul", "ol", "blockquote", "pre", "table",
        "tr", "thead", "tbody", "tfoot", "section", "article",
        "aside", "header", "footer", "main", "nav", "figure",
        "figcaption", "details", "summary", "dl", "dt", "dd",
        0
    };
    for(int i = 0; bl[i]; i++) {
        if(tag_is(name, bl[i])) return 1;
    }
    return 0;
}

/* ── Output a Unicode codepoint → CP1251 for font ── */
static void out_codepoint(uint32_t cp) {
    if(cp >= 0x0410 && cp <= 0x042F)
        out_char((char)(cp - 0x410 + 0xC0));  /* А-Я */
    else if(cp >= 0x0430 && cp <= 0x044F)
        out_char((char)(cp - 0x430 + 0xE0));  /* а-я */
    else if(cp == 0x0401) out_char((char)0xA8);  /* Ё */
    else if(cp == 0x0451) out_char((char)0xB8);  /* ё */
    else if(cp == 0x2013) out_char((char)0x96);  /* – en dash */
    else if(cp == 0x2014) out_char((char)0x97);  /* — em dash */
    else if(cp == 0x2026) out_char((char)0x85);  /* … ellipsis */
    else if(cp == 0x2018) out_char((char)0x91);  /* ' left single quote */
    else if(cp == 0x2019) out_char((char)0x92);  /* ' right single quote */
    else if(cp == 0x201C) out_char((char)0x93);  /* " left double quote */
    else if(cp == 0x201D) out_char((char)0x94);  /* " right double quote */
    else if(cp == 0x2022) out_char((char)0x95);  /* • bullet */
    else if(cp == 0x2122) out_char((char)0x99);  /* ™ trade mark */
    else if(cp == 0x00A9) out_char((char)0xA9);  /* © copyright */
    else if(cp == 0x00AE) out_char((char)0xAE);  /* ® registered */
    else if(cp == 0x00AB) out_char((char)0xAB);  /* « left angle quote */
    else if(cp == 0x00BB) out_char((char)0xBB);  /* » right angle quote */
    else if(cp < 0x100) out_char((char)cp);       /* Latin-1 */
    else out_char('?');
}

/* ── Render the HTML ── */
void html_parse_and_render(const char *html)
{
    if(!html || !html[0]) {
        html_buf[0] = '\0';
        html_len = 0;
        return;
    }

    out_pos = 0;
    out_line = 0;
    out_col = 0;
    html_link_count = 0;
    active_link_idx = -1;
    const char *p = html;
    int skip_depth = 0;       /* >0 when inside skip/script/style/head */
    int svg_depth = 0;        /* >0 when inside <svg> */
    int pre_depth = 0;        /* >0 when inside <pre> */
    int list_depth = 0;       /* nesting depth of ul/ol */
    int last_was_newline = 1; /* track if we just output a newline */

    while(*p) {
        /* ── Inside skip zone: look for closing tag ── */
        if(skip_depth > 0) {
            if(*p == '<') {
                const char *tag_start = p + 1;
                int tclosing = (*tag_start == '/');
                if(tclosing) tag_start++;

                /* Implicitly close <head> when <body> or <html> appears */
                if(!tclosing && (tag_is(tag_start, "body") || tag_is(tag_start, "html"))) {
                    skip_depth = 0;
                    /* Fall through to normal tag processing below */
                } else {
                    if(is_skip_tag(tag_start)) {
                        if(tclosing) {
                            skip_depth--;
                            if(skip_depth < 0) skip_depth = 0;
                        } else {
                            skip_depth++;
                        }
                    }
                    /* Skip to end of this tag */
                    while(*p && *p != '>') p++;
                    if(*p == '>') p++;
                    continue;
                }
            }
            if(skip_depth > 0) {
                p++;
                continue;
            }
        }

        /* ── Inside SVG: look for </svg> ── */
        if(svg_depth > 0) {
            if(*p == '<') {
                const char *tag_start = p + 1;
                int closing = (*tag_start == '/');
                if(closing) tag_start++;
                if(is_svg_tag(tag_start)) {
                    if(closing) {
                        svg_depth--;
                        if(svg_depth < 0) svg_depth = 0;
                    } else {
                        svg_depth++;
                    }
                }
                while(*p && *p != '>') p++;
                if(*p == '>') p++;
                continue;
            }
            p++;
            continue;
        }

        /* ── Comment ── */
        if(p[0] == '<' && p[1] == '!' && p[2] == '-' && p[3] == '-') {
            p += 4;
            while(*p && !(p[0] == '-' && p[1] == '-' && p[2] == '>')) p++;
            if(*p) p += 3;
            continue;
        }

        /* ── DOCTYPE ── */
        if(p[0] == '<' && p[1] == '!') {
            while(*p && *p != '>') p++;
            if(*p == '>') p++;
            continue;
        }

        /* ── Tag ── */
        if(*p == '<') {
            p++;
            int closing = 0;
            if(*p == '/') { closing = 1; p++; }

            /* Read tag name */
            char tagname[32];
            int tlen = 0;
            while(*p && *p != ' ' && *p != '>' && *p != '/' &&
                  *p != '\t' && *p != '\n' && *p != '\r' &&
                  tlen < 31) {
                tagname[tlen++] = *p++;
            }
            tagname[tlen] = 0;

            if(tlen == 0) { /* malformed, skip '<' */
                out_char('<');
                continue;
            }

            /* ── Skip tags ── */
            if(is_skip_tag(tagname)) {
                if(closing) {
                    skip_depth--;
                    if(skip_depth < 0) skip_depth = 0;
                } else {
                    skip_depth++;
                }
                /* Consume rest of opening tag */
                while(*p && *p != '>') p++;
                if(*p == '>') p++;
                continue;
            }

            /* ── SVG ── */
            if(is_svg_tag(tagname)) {
                if(closing) {
                    svg_depth--;
                    if(svg_depth < 0) svg_depth = 0;
                } else {
                    svg_depth++;
                }
                while(*p && *p != '>') p++;
                if(*p == '>') p++;
                continue;
            }

            /* ── Title — skip content between <title> and </title> ── */
            if(tag_is(tagname, "title")) {
                if(!closing) {
                    /* Skip until </title> */
                    while(*p) {
                        if(*p == '<') {
                            const char *t = p + 1;
                            if(*t == '/' && tag_is(t + 1, "title")) {
                                while(*p && *p != '>') p++;
                                if(*p == '>') p++;
                                break;
                            }
                        }
                        p++;
                    }
                }
                continue;
            }

            /* ── Pre ── */
            if(tag_is(tagname, "pre")) {
                if(closing) pre_depth--;
                else pre_depth++;
                while(*p && *p != '>') p++;
                if(*p == '>') p++;
                continue;
            }

            /* Read attributes before consuming the tag */
            char attr_href[256] = {0};
            char attr_alt[128] = {0};
            char attr_value[256] = {0};

            if(!closing) {
                while(*p && *p != '>') {
                    /* Skip whitespace */
                    while(*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++;
                    if(*p == '>' || *p == '/') break;

                    /* Read attribute name */
                    char attrname[32];
                    int alen = 0;
                    while(*p && *p != '=' && *p != ' ' && *p != '>' &&
                          *p != '/' && *p != '\t' && alen < 31)
                        attrname[alen++] = *p++;
                    attrname[alen] = 0;

                    /* Skip = and value */
                    if(*p == '=') {
                        p++;
                        p = read_attr_value(p, attr_value, sizeof(attr_value));
                        if(tag_is(tagname, "a") && tag_is(attrname, "href"))
                            strcpy(attr_href, attr_value);
                        if(tag_is(tagname, "img") && tag_is(attrname, "alt"))
                            strcpy(attr_alt, attr_value);
                    }
                    /* If no =, just skip to next attr */
                }
            }

            /* Consume '>' or '/>' */
            while(*p && *p != '>') p++;
            if(*p == '>') p++;

            /* ── Render output based on tag ── */

            if(closing) {
                /* Closing tags — insert appropriate whitespace */
                if(tag_is(tagname, "p") || tag_is(tagname, "div") ||
                   tag_is(tagname, "blockquote")) {
                    if(!last_was_newline) out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "h1") || tag_is(tagname, "h2") ||
                          tag_is(tagname, "h3") || tag_is(tagname, "h4") ||
                          tag_is(tagname, "h5") || tag_is(tagname, "h6")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "li")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "ul") || tag_is(tagname, "ol")) {
                    list_depth--;
                    if(list_depth < 0) list_depth = 0;
                    if(!last_was_newline) out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "tr")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "table")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "blockquote")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "dl") || tag_is(tagname, "dd")) {
                    if(!last_was_newline) out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "dt")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "pre")) {
                    out_nls(1);
                    last_was_newline = 1;
                } else if(tag_is(tagname, "summary")) {
                    if(!last_was_newline) out_nls(1);
                    last_was_newline = 1;
                }
                continue;
            }

            /* Opening tags */
            if(tag_is(tagname, "br")) {
                out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "hr")) {
                out_nls(1);
                out_str("========================================");
                out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "p") || tag_is(tagname, "div") ||
                      tag_is(tagname, "blockquote") ||
                      tag_is(tagname, "section") || tag_is(tagname, "article") ||
                      tag_is(tagname, "aside") || tag_is(tagname, "header") ||
                      tag_is(tagname, "footer") || tag_is(tagname, "main") ||
                      tag_is(tagname, "nav")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
                if(tag_is(tagname, "blockquote")) {
                    out_indent(1);
                }
            } else if(tag_is(tagname, "h1")) {
                out_nls(1);
                out_str("========================================");
                out_nls(1);
            } else if(tag_is(tagname, "h2")) {
                out_nls(1);
                out_str("----------------------------------------");
                out_nls(1);
            } else if(tag_is(tagname, "h3") || tag_is(tagname, "h4") ||
                      tag_is(tagname, "h5") || tag_is(tagname, "h6")) {
                out_nls(1);
            } else if(tag_is(tagname, "li")) {
                out_nls(1);
                for(int i = 0; i < list_depth && i < 4; i++) out_str("  ");
                out_str("* ");
                last_was_newline = 0;
            } else if(tag_is(tagname, "ul") || tag_is(tagname, "ol")) {
                list_depth++;
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "tr")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "td") || tag_is(tagname, "th")) {
                out_str(" | ");
                last_was_newline = 0;
            } else if(tag_is(tagname, "a")) {
                if(!closing && attr_href[0]) {
                    begin_link(attr_href);
                } else if(closing) {
                    end_link();
                }
            } else if(tag_is(tagname, "img")) {
                if(attr_alt[0]) {
                    out_char('[');
                    out_str(attr_alt);
                    out_char(']');
                } else {
                    out_str("[image]");
                }
            } else if(tag_is(tagname, "table")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "dl")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "dt")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "dd")) {
                out_str("  ");
                last_was_newline = 0;
            } else if(tag_is(tagname, "summary")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            } else if(tag_is(tagname, "details")) {
                if(!last_was_newline) out_nls(1);
                last_was_newline = 1;
            }

            /* skip self-closing already consumed '>' */
            if(is_self_closing(tagname)) {
                /* nothing more to do */
            }
            continue;
        }

        /* ── Text content ── */
        if(*p == '&') {
            int ent = decode_entity(p + 1);
            if(ent >= 0) {
                out_codepoint((uint32_t)ent);
                /* Advance past entity */
                p++;
                while(*p && *p != ';') p++;
                if(*p == ';') p++;
                last_was_newline = 0;
                continue;
            }
            out_char(*p++);
            last_was_newline = 0;
            continue;
        }

        if(*p == '\n' || *p == '\r') {
            if(pre_depth > 0) {
                out_char('\n');
                last_was_newline = 1;
            }
            /* In non-pre mode, collapse whitespace */
            p++;
            continue;
        }

        if(*p == '\t' || *p == ' ') {
            if(pre_depth > 0) {
                out_char(*p);
            } else {
                /* Collapse multiple spaces into one */
                if(!last_was_newline) {
                    out_char(' ');
                }
            }
            p++;
            /* Skip additional whitespace in non-pre mode */
            if(pre_depth == 0) {
                while(*p == ' ' || *p == '\t') p++;
            }
            last_was_newline = 0;
            continue;
        }

        /* Regular character — decode UTF-8 → CP1251 for Cyrillic */
        if((unsigned char)*p >= 0xC0) {
            /* 2+ byte UTF-8 sequence */
            unsigned char c0 = (unsigned char)*p++;
            uint32_t cp = 0;
            int extra = 0;
            if((c0 & 0xE0) == 0xC0) { cp = c0 & 0x1F; extra = 1; }
            else if((c0 & 0xF0) == 0xE0) { cp = c0 & 0x0F; extra = 2; }
            else if((c0 & 0xF8) == 0xF0) { cp = c0 & 0x07; extra = 3; }
            for(int i = 0; i < extra; i++) {
                if(!*p) break;
                if(((unsigned char)*p & 0xC0) == 0x80)
                    cp = (cp << 6) | ((unsigned char)*p++ & 0x3F);
                else break;
            }
            out_codepoint(cp);
        } else {
            out_char(*p++);
        }
        last_was_newline = 0;
    }

    /* Ensure buffer is terminated */
    if(out_pos > 0 && html_buf[out_pos - 1] != '\n')
        out_char('\n');

    html_buf[out_pos] = '\0';
    html_len = out_pos;
}
