---
/**
 * Render parts of the JSON schema into a table, given a `group` to render.
 * Properties in the schema can be assigned to groups by adding a new "groups": ["group1", "group2"] key to them.
 * This way, you can split schema definition into semantic groups.
 */
import {
    type JSONSchema7,
    type JSONSchema7Definition,
    type JSONSchema7TypeName,
    type JSONSchema7Type,
} from 'json-schema';
import MarkdownRenderer from './MarkdownRenderer.astro';
import rawSchema from '../values.schema.json';

// the 'as any' isn't pretty but needed because our schema doesn't actually conform to the type.
// it's still nice to use the type to get at least some type hinting when developing.
let schema: JSONSchema7 = rawSchema as any;

const { group, fieldColumnClass } = Astro.props;

/** Example: 'boolean' -> 'Boolean'. */
function capitalizeFirst(str: string) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
 * A useful representation of a schema type.
 * Plain types are uppercased. Lists of types are joined.
 * If type is 'string' but there are also enum vals, the enum vals are returned instead.
 */
function typeToString(
    type: JSONSchema7TypeName | JSONSchema7TypeName[] | undefined,
    enumvals: JSONSchema7Type[] | undefined,
) {
    if (type === undefined) return '';
    if (Array.isArray(type)) {
        return type.map((t) => capitalizeFirst(String(t))).join(', ');
    }
    if (type === 'string' && enumvals !== undefined) {
        return enumvals?.map((enumval) => String(enumval)).join(', ');
    }
    return capitalizeFirst(String(type));
}

/** A row in the table. */
interface Row {
    key: string;
    type?: string;
    default?: string;
    description?: string;
}

const rows: Row[] = [];

/**
 * Recursive function to traverse the schema properties and extract rows for the table.
 * @param prefix Accumulates the tree path during recursion, start of with "".
 * @param key The key of the property currently observed.
 * @param definition The definition of the property currently observed.
 */
function addSelfAndChildren(prefix: string, key: string, definition: JSONSchema7Definition) {
    if (typeof definition === 'object' && definition !== null) {
        if ('placeholder' in definition) {
            key = `<${definition.placeholder}>`;
        }
        if ('docsIncludePrefix' in definition && definition.docsIncludePrefix === false) {
            prefix = '';
        }
        if ('groups' in definition && Array.isArray(definition.groups) && definition.groups.includes(group)) {
            var def = definition.default !== undefined ? String(definition.default) : '';
            if (definition.type === 'string' && def !== '') {
                def = `"${def}"`;
            }
            rows.push({
                key: `${prefix}${key}`,
                type: typeToString(definition.type, definition.enum),
                default: def,
                description: definition.description,
            });
        }
        if ('properties' in definition && definition.properties) {
            Object.entries(definition.properties).forEach(([k, d]) => addSelfAndChildren(`${prefix}${key}.`, k, d));
        }
        if ('patternProperties' in definition && definition.patternProperties) {
            Object.entries(definition.patternProperties).forEach(([k, d]) =>
                addSelfAndChildren(`${prefix}${key}.`, k, d),
            );
        }
        if ('items' in definition && definition.items !== undefined && typeof definition.items === 'object') {
            const items = definition.items;
            if ('length' in items) {
                // filter out arrays
                return;
            }
            addSelfAndChildren(`${prefix}${key}.`, '[]', items);
        }
    }
}

if (schema.definitions) {
    Object.entries(schema.definitions).forEach(([_, definition]) => {
        if (typeof definition === 'object' && definition.properties) {
            Object.entries(definition.properties).forEach(([key, definition]) => {
                addSelfAndChildren('', key, definition);
            });
        }
    });
}

// start of recursing with the top level properties in the schema.
if (schema.properties) {
    Object.entries(schema.properties).forEach(([key, definition]) => {
        addSelfAndChildren('', key, definition);
    });
}
---

<div class='overflow-x-scroll'>
    <table class='min-w-[700px]'>
        <thead>
            <tr>
                <th class={fieldColumnClass}>Field</th>
                <th>Type</th>
                <th>Default</th>
                <th>Description</th>
            </tr>
        </thead>
        <tbody>
            {
                rows.map((row) => (
                    <tr>
                        <td>
                            <code>{row.key}</code>
                        </td>
                        <td>{row.type}</td>
                        <td>{row.default}</td>
                        <td>
                            <MarkdownRenderer content={row.description} />
                        </td>
                    </tr>
                ))
            }
        </tbody>
    </table>
</div>
