<?php
/**
 * Plugin Name:       Auto Table of Contents by ConvertAtlas
 * Plugin URI:        https://convertatlas.org/en/wordpress/auto-table-of-contents
 * Description:       Automatically builds a table of contents from H2/H3 headings in your posts and adds anchor links. Use [ca_toc] or let it auto-insert before the first heading.
 * Version:           1.0.0
 * Requires at least: 5.5
 * Requires PHP:      7.4
 * Author:            ConvertAtlas
 * Author URI:        https://convertatlas.org
 * License:           GPL-2.0-or-later
 * Text Domain:       ca-toc
 *
 * Built with convertatlas.org — free developer tools & templates.
 */

if (!defined('ABSPATH')) { exit; }

final class CA_Table_Of_Contents {

    private $headings = array();

    public static function init() {
        $self = new self();
        add_shortcode('ca_toc', array($self, 'shortcode'));
        // Process content: add ids to headings and collect them.
        add_filter('the_content', array($self, 'process'), 9);
        add_action('wp_head', array($self, 'styles'));
    }

    private function slugify($text) {
        $text = sanitize_title($text);
        return $text ? $text : 'section';
    }

    public function process($content) {
        if (!is_singular() || empty($content)) return $content;
        $this->headings = array();
        $used = array();

        $content = preg_replace_callback('/<h([23])([^>]*)>(.*?)<\/h\1>/is', function ($m) use (&$used) {
            $level = (int) $m[1];
            $attrs = $m[2];
            $inner = $m[3];
            $text  = trim(wp_strip_all_tags($inner));
            if ($text === '') return $m[0];

            // Build a unique id
            $base = $this->slugify($text);
            $id = $base; $i = 2;
            while (isset($used[$id])) { $id = $base . '-' . $i; $i++; }
            $used[$id] = true;

            $this->headings[] = array('level' => $level, 'text' => $text, 'id' => $id);

            // Only add id if the heading doesn't already have one
            if (stripos($attrs, 'id=') !== false) {
                return $m[0];
            }
            return '<h' . $level . $attrs . ' id="' . esc_attr($id) . '">' . $inner . '</h' . $level . '>';
        }, $content);

        return $content;
    }

    public function render_toc() {
        if (count($this->headings) < 2) return '';
        $out = '<nav class="ca-toc" aria-label="Table of contents"><p class="ca-toc-title">Table of contents</p><ul>';
        foreach ($this->headings as $h) {
            $cls = $h['level'] === 3 ? ' class="ca-toc-sub"' : '';
            $out .= '<li' . $cls . '><a href="#' . esc_attr($h['id']) . '">' . esc_html($h['text']) . '</a></li>';
        }
        $out .= '</ul></nav>';
        return $out;
    }

    public function shortcode($atts) {
        // Ensure content has been processed so headings are collected.
        if (empty($this->headings) && is_singular()) {
            $post = get_queried_object();
            if ($post && isset($post->post_content)) {
                $this->process($post->post_content);
            }
        }
        return $this->render_toc();
    }

    public function styles() {
        echo "\n<style id=\"ca-toc-css\">.ca-toc{background:#f7f7f9;border:1px solid #e6e6ec;border-radius:10px;padding:1rem 1.25rem;margin:1.5rem 0;font-size:.95rem}.ca-toc-title{font-weight:700;margin:0 0 .5rem}.ca-toc ul{margin:0;padding-left:1.1rem}.ca-toc li{margin:.25rem 0}.ca-toc .ca-toc-sub{margin-left:1rem;list-style:circle}.ca-toc a{color:#2563eb;text-decoration:none}.ca-toc a:hover{text-decoration:underline}</style>\n";
    }
}

add_action('init', array('CA_Table_Of_Contents', 'init'));