#include "inline_parse.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

void inline_runs_init(InlineRunList *list) {
    list->runs = NULL;
    list->count = 0;
    list->cap = 0;
}

void inline_runs_free(InlineRunList *list) {
    free(list->runs);
    list->runs = NULL;
    list->count = 0;
    list->cap = 0;
}

static void push_run(InlineRunList *list, RunKind kind, int rawStart, int rawEnd, int contentStart, int contentEnd) {
    if (list->count >= list->cap) {
        list->cap = list->cap > 0 ? list->cap * 2 : 8;
        list->runs = realloc(list->runs, (size_t)list->cap * sizeof(InlineRun));
    }
    InlineRun *r = &list->runs[list->count++];
    r->kind = kind;
    r->rawStart = rawStart;
    r->rawEnd = rawEnd;
    r->contentStart = contentStart;
    r->contentEnd = contentEnd;
}

/* Finds the next unescaped occurrence of `c`, starting at `from`. -1 if not found. */
static int find_char(const char *text, int len, int from, char c) {
    for (int i = from; i < len; i++) {
        if (text[i] == '\\' && i + 1 < len) { i++; continue; }
        if (text[i] == c) return i;
    }
    return -1;
}

/* Finds the next unescaped occurrence of the two-char delimiter `cc`. -1 if not found. */
static int find_double(const char *text, int len, int from, char c) {
    for (int i = from; i < len - 1; i++) {
        if (text[i] == '\\' && i + 1 < len) { i++; continue; }
        if (text[i] == c && text[i + 1] == c) return i;
    }
    return -1;
}

/* If text[i..] starts with "http://" or "https://", returns the length of the URL run
   (stopping at whitespace/'<'/'>', then trimming trailing punctuation that's usually not
   meant to be part of the URL, e.g. the '.' ending a sentence). Returns 0 if no match. */
static int match_autolink_len(const char *text, int i, int len) {
    static const char *const schemes[] = { "https://", "http://" };
    for (int s = 0; s < 2; s++) {
        int slen = (int)strlen(schemes[s]);
        if (i + slen <= len && memcmp(text + i, schemes[s], (size_t)slen) == 0) {
            int j = i + slen;
            while (j < len && !isspace((unsigned char)text[j]) && text[j] != '<' && text[j] != '>') j++;
            while (j > i + slen && strchr(".,;:!?)", text[j - 1])) j--;
            return j - i;
        }
    }
    return 0;
}

void inline_parse(const char *text, int len, InlineRunList *out) {
    inline_runs_init(out);
    int i = 0;
    int plainStart = 0;

    while (i < len) {
        char c = text[i];
        int matched = 0;

        if (c == '`') {
            int j = find_char(text, len, i + 1, '`');
            if (j >= 0 && j > i + 1) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_CODE, i, j + 1, i + 1, j);
                i = j + 1;
                plainStart = i;
                matched = 1;
            }
        } else if ((c == '*' || c == '_') && i + 2 < len && text[i + 1] == c && text[i + 2] == c) {
            int j = -1;
            for (int k = i + 3; k < len - 2; k++) {
                if (text[k] == c && text[k + 1] == c && text[k + 2] == c) { j = k; break; }
            }
            if (j >= 0 && j > i + 3) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_BOLD_ITALIC, i, j + 3, i + 3, j);
                i = j + 3;
                plainStart = i;
                matched = 1;
            }
        } else if ((c == '*' || c == '_') && i + 1 < len && text[i + 1] == c) {
            int j = find_double(text, len, i + 2, c);
            if (j >= 0 && j > i + 2) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_BOLD, i, j + 2, i + 2, j);
                i = j + 2;
                plainStart = i;
                matched = 1;
            }
        } else if (c == '*' || c == '_') {
            int j = find_char(text, len, i + 1, c);
            if (j >= 0 && j > i + 1) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_ITALIC, i, j + 1, i + 1, j);
                i = j + 1;
                plainStart = i;
                matched = 1;
            }
        } else if (c == '[') {
            int closeBracket = find_char(text, len, i + 1, ']');
            if (closeBracket >= 0 && closeBracket > i + 1 && closeBracket + 1 < len && text[closeBracket + 1] == '(') {
                int closeParen = find_char(text, len, closeBracket + 2, ')');
                if (closeParen >= 0) {
                    if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                    push_run(out, RUN_LINK, i, closeParen + 1, i + 1, closeBracket);
                    i = closeParen + 1;
                    plainStart = i;
                    matched = 1;
                }
            }
        } else if (c == '~' && i + 1 < len && text[i + 1] == '~') {
            int j = find_double(text, len, i + 2, '~');
            if (j >= 0 && j > i + 2) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_STRIKE, i, j + 2, i + 2, j);
                i = j + 2;
                plainStart = i;
                matched = 1;
            }
        } else if (c == '~') {
            int j = find_char(text, len, i + 1, '~');
            if (j >= 0 && j > i + 1) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_SUB, i, j + 1, i + 1, j);
                i = j + 1;
                plainStart = i;
                matched = 1;
            }
        } else if (c == '=' && i + 1 < len && text[i + 1] == '=') {
            int j = find_double(text, len, i + 2, '=');
            if (j >= 0 && j > i + 2) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_HIGHLIGHT, i, j + 2, i + 2, j);
                i = j + 2;
                plainStart = i;
                matched = 1;
            }
        } else if (c == '^') {
            int j = find_char(text, len, i + 1, '^');
            if (j >= 0 && j > i + 1) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                push_run(out, RUN_SUPER, i, j + 1, i + 1, j);
                i = j + 1;
                plainStart = i;
                matched = 1;
            }
        } else if (c == 'h') {
            int alen = match_autolink_len(text, i, len);
            if (alen > 0) {
                if (i > plainStart) push_run(out, RUN_PLAIN, plainStart, i, plainStart, i);
                /* No markers to hide/reveal for a bare-URL autolink: raw range == content range. */
                push_run(out, RUN_LINK, i, i + alen, i, i + alen);
                i += alen;
                plainStart = i;
                matched = 1;
            }
        } else if (c == '\\' && i + 1 < len) {
            i += 2;
            continue;
        }

        if (!matched) i++;
    }

    if (len > plainStart) push_run(out, RUN_PLAIN, plainStart, len, plainStart, len);
}
