# -*- coding: utf-8 -*-
"""Shared help texts and a small help button used across the tools."""

from qgis.PyQt.QtWidgets import QPushButton, QDialog, QVBoxLayout, QTextBrowser, QDialogButtonBox


HELP = {
    "loader": (
        "1 - Load GEE data & indices",
        """<h3>Load GEE data &amp; indices</h3>
<p>Builds an analysis-ready image stack from Google Earth Engine and loads
it for the rest of the workflow.</p>
<b>Steps</b>
<ol>
<li><b>Satellite &amp; dates:</b> choose a sensor (Sentinel-2, Landsat 8/9,
Sentinel-1 SAR, or the multi-sensor fusion), a date range, and a cloud
threshold. A median composite is built over that period.</li>
<li><b>AOI:</b> pick the area. <i>Administrative unit</i> uses the bundled
Cambodia boundaries (offline, fast). <i>Canvas extent</i> and
<i>Draw polygon</i> let you define any area; the tool detects which
province/district it falls in so the map layout can reuse it.</li>
<li><b>Original bands:</b> tick only the bands you need. RGB + NIR is a
good default for a fast preview; add SWIR and red-edge for classification.</li>
<li><b>Spectral indices:</b> NDVI and NDWI are pre-selected. Add others as
needed - each is computed on GEE and added as a band.</li>
<li><b>Custom bands:</b> <i>Custom index ...</i> lets you write your own
band math (e.g. <tt>(N - S2b) / (N + S2b)</tt>) using the sensor's band
tokens. <i>Custom temporal metrics ...</i> evaluates an expression on every
image in the date range and then reduces it through time - mean, median,
min, max, std-dev, a percentile, or the amplitude (max-min). A P90 NDVI or
an NDVI amplitude band is often the single best feature for separating
paddy rice from other crops. Custom bands are appended last, so neither the
"important bands" preset nor un-ticking original bands can drop them.</li>
<li><b>Build stack:</b> the primary button. It loads preview tiles
immediately and downloads a local multiband GeoTIFF in the background so
QGIS stays responsive.</li>
</ol>
<p><b>Tip:</b> the local GeoTIFF carries real band names (NDVI, SAVI, ...),
so the Symbology dropdowns show names, not "Band 20".</p>"""),

    "sampling": (
        "2 - Create sample points",
        """<h3>Create sample points</h3>
<p>Generates the training/validation points for classification and
accuracy assessment.</p>
<b>Key choices</b>
<ul>
<li><b>Sample size (Cochran):</b> n = z&sup2;p(1-p)/e&sup2;. Defaults
(p=0.85, e=0.05) give a statistically sound sample; lower e for more
points.</li>
<li><b>Method:</b> <i>Stratified by ESA WorldCover</i> (recommended) spreads
points across existing land-cover classes so rare classes are represented.</li>
<li><b>Allocation:</b> <i>Proportional to area</i> is the Olofsson (2014)
recommendation for validation; <i>Equal per class</i> is better for
training. For a clean design, make two sets - one of each.</li>
<li><b>Min per class:</b> Olofsson suggests 50-100 per class for
validation.</li>
</ul>
<p><b>Why the created total differs from the computed n.</b> The Cochran n
is a <i>whole-sample</i> target. It is split across the classes found in
the AOI (largest-remainder rounding, so the split itself sums to exactly
n), and then every class is raised to <i>Min per class</i>. Nothing is
taken from the big classes to pay for that, so with k classes the total is
at least k x the minimum and normally lands <b>above</b> n. That is the
design working as intended - a class with three points has no usable
user's accuracy. Set <i>Min per class</i> to 0 to see the pure
proportional split.</p>
<p>The one case that <i>does</i> matter is a total <b>below</b> the
allocation: it means a stratum ran out of pixels and came back short. That
is reported explicitly as a warning after the run, with the class and the
shortfall.</p>
<p>Points get a 70/30 train/valid split and an ESA WorldCover hint field
to speed up labeling.</p>"""),

    "labeling": (
        "3 - Label sample points",
        """<h3>Label sample points</h3>
<p>Assign a land-cover class to each point.</p>
<ul>
<li>The canvas is prepared with ESA WorldCover shown and the heavy
imagery layers hidden. Click a class button (or the Level-2 dropdown for
detailed LU2003 classes).</li>
<li><b>4-view window:</b> shows the same point in four synchronized
panels (NDVI, NDWI, true colour, Google Satellite) so you can judge a
point from several angles at once - the best way to avoid mislabels.</li>
<li><b>GCP shapefile:</b> load field-surveyed control points; the nearest
GCP shows as a hint, and you can bulk-transfer GCP classes to nearby
points.</li>
</ul>
<p><b>Tip:</b> a point that looks like cropland on satellite but reads as
water on NDWI deserves a second look.</p>"""),

    "gcp_table": (
        "Attribute tables - sample points & GCPs",
        """<h3>What the attribute tables look like</h3>

<h4>1. The <tt>sample_points</tt> layer (created by Tool 2)</h4>
<p>Tool 2 builds this layer for you and Tool 3 fills in the class columns.
You normally never create it by hand.</p>
<table border="1" cellpadding="4" cellspacing="0">
<tr bgcolor="#eeeeee">
<th>point_id</th><th>class_code</th><th>class_name</th>
<th>lu_code</th><th>lu_name</th><th>use</th>
<th>wc_code</th><th>wc_hint</th></tr>
<tr><td>1</td><td>1</td><td>Forest</td><td>110</td>
<td>Evergreen forest</td><td>train</td><td>10</td><td>Tree cover</td></tr>
<tr><td>2</td><td>2</td><td>Agriculture</td><td>210</td>
<td>Paddy field</td><td>valid</td><td>40</td><td>Cropland</td></tr>
<tr><td>3</td><td>5</td><td>Water</td><td>510</td>
<td>River</td><td>train</td><td>80</td><td>Water</td></tr>
<tr><td>4</td><td>0</td><td><i>(empty)</i></td><td>0</td>
<td><i>(empty)</i></td><td>train</td><td>50</td><td>Built-up</td></tr>
</table>
<ul>
<li><b>point_id</b> - running number, written by Tool 2.</li>
<li><b>class_code / class_name</b> - <u>the Level-1 label you assign</u>.
<b>0 / empty means still unlabeled</b> (drawn grey on the canvas). This is
the column the classifier actually trains on.</li>
<li><b>lu_code / lu_name</b> - optional Level-2 LU2003 detail. Leave at 0 if
you only work at Level 1.</li>
<li><b>use</b> - <tt>train</tt> or <tt>valid</tt>, split automatically by
Tool 2. Do not edit unless you know why: the accuracy assessment relies on
the validation points never having trained the model.</li>
<li><b>wc_code / wc_hint</b> - ESA WorldCover suggestion, a hint only. It is
<i>not</i> your label and is ignored by the classifier.</li>
</ul>
<p><b>Level-1 codes:</b> 1 Forest, 2 Agriculture, 3 Barren, 4 Built-up,
5 Water, 6 Grassland, 7 Shrubland, 8 Other.</p>

<h4>2. A GCP file you bring from the field</h4>
<p>Any point file QGIS can open works: <tt>.shp</tt>, <tt>.gpkg</tt>,
<tt>.geojson</tt>, or a <tt>.csv</tt> with X/Y columns. It needs
<b>exactly one</b> thing: a column holding the land-cover class. Everything
else is yours to keep.</p>
<table border="1" cellpadding="4" cellspacing="0">
<tr bgcolor="#eeeeee"><th>id</th><th>class</th><th>note</th>
<th>date</th></tr>
<tr><td>1</td><td>Forest</td><td>bamboo edge</td><td>2025-11-02</td></tr>
<tr><td>2</td><td>Agriculture</td><td>wet season rice</td>
<td>2025-11-02</td></tr>
<tr><td>3</td><td>Water</td><td>village pond</td><td>2025-11-03</td></tr>
</table>
<p>The class column may hold <b>either</b> names (<tt>Forest</tt>,
<tt>Agriculture</tt>, ... - matching is case-insensitive and accepts partial
names) <b>or</b> the numeric codes 1-8. You pick which column it is right
after choosing the file.</p>
<p><b>CRS:</b> anything - GCPs are reprojected on the fly. A GPS export in
EPSG:4326 is fine; so is a UTM 48N file.</p>

<h4>3. How the two meet</h4>
<ul>
<li>Loading GCPs shows the nearest one as a hint while you label
manually.</li>
<li><b>Apply GCP labels</b> asks for a radius in metres and copies the GCP
class into <tt>class_code</tt> / <tt>class_name</tt> for every sample point
with a GCP inside that radius. Points with no GCP nearby stay
unlabeled.</li>
<li>Pick the radius to match your field precision - 30-50 m for a
handheld GPS in open ground; smaller in fragmented landscapes, or you will
drag a neighbouring class onto the point.</li>
</ul>"""),

    "classify": (
        "4 - Classify & accuracy",
        """<h3>Classify &amp; accuracy</h3>
<b>Supervised</b> (uses your labeled points): Random Forest, Gradient Tree
Boost, CART, or SVM. "Compare all" ranks them. The report gives the
confusion matrix, overall accuracy, kappa, per-class producer/user
accuracy, and the area-adjusted accuracy with 95% CIs (Olofsson 2014).
<br><br>
<b>Unsupervised</b> (no labels): K-Means and other clustering algorithms
group pixels by spectral similarity. Then <b>Reclassify clusters</b> maps
each cluster to a land-cover class - several clusters can map to one class.
<br><br>
<b>Post-processing:</b> majority filter, minimum mapping unit, hole
filling, and boundary smoothing. Accuracy is assessed on the final
(post-processed) map. Use <b>Flag misclassified points</b> to find likely
labeling errors, fix them in Tool 3, and re-run.
<br><br>
<b>Improving accuracy:</b> check the J-M separability (Tool 5) first -
classes below 1.0 will always confuse. Add discriminating bands (temporal
NDVI, SAR) rather than just more points."""),

    "signatures": (
        "5 - Signatures & separability",
        """<h3>Signatures &amp; separability</h3>
<p>Diagnoses class separability <i>before</i> you classify.</p>
<ul>
<li><b>Mean signatures:</b> the average spectral profile of each class,
sampled from the stack at your labeled points.</li>
<li><b>Jeffries-Matusita matrix:</b> every class pair scored 0 (identical)
to 2 (perfectly separable). Pairs below 1.0 will confuse no matter how
many points you add - either merge them or add a band that separates
them.</li>
<li><b>Live plot:</b> interactive curves; click a legend entry to hide a
class; export to SVG/PDF for papers.</li>
</ul>"""),

    "layout": (
        "6 - Map Layout",
        """<h3>Map Layout</h3>
<p>Builds a print-ready map of the classified result.</p>
<ul>
<li><b>Area source:</b> an administrative unit, a GEE asset, the current
canvas extent, or a drawn polygon (the AOI from Tool 1 carries through
automatically).</li>
<li><b>Basemap:</b> <i>Esri Shaded Relief</i> is on by default - it works
anywhere in the world and keeps thematic colours readable. Tick
<i>Google Satellite</i> for imagery; with both on, imagery draws over the
relief. Attribution is added to the sources line for you.</li>
<li><b>Insets:</b> Inset 1 shows the wider context with the level-1 unit
highlighted; Inset 2 zooms to the level-2 unit with its child highlighted.
Each highlight has a colour and a fill style (solid or hatched).</li>
<li><b>Institution:</b> type your institution name; it prints beside the
emblem.</li>
<li>The LULC legend is inserted after the context layers, and the accuracy
line sits at the bottom-centre of the map frame.</li>
</ul>

<h4>Using this outside Cambodia</h4>
<p>The layout style - frame, grid, scale bar, north arrow, legend, insets -
is not Cambodia-specific. Two ways to work anywhere:</p>
<ol>
<li><b>No setup at all:</b> choose <i>Current map canvas extent</i>,
<i>Drawn polygon / AOI from Tool 1</i>, or <i>GEE asset</i> as the area
source. These never touch the bundled boundaries. For the insets, the
layer dropdown lists every polygon layer open in your project, so point
them at your own boundaries.</li>
<li><b>Full cascade:</b> open the <b>Setup</b> tab, switch <i>Boundary
data</i> to <i>Custom folder</i> and pick a folder holding one polygon file
per level (country, province/state, district, commune). GADM, FAO GAUL and
OCHA COD downloads work untouched - the name, code and parent-code fields
are detected automatically, and the report shows exactly what was found
before you commit to it. The cascade, the auto-configured insets and the
whole layout then behave exactly as they do for Cambodia.</li>
</ol>
<p><b>Tip:</b> if a level shows every unit instead of only the children of
the one above, the detection could not find a parent-code field in that
file. The Setup report flags this; add a column holding the parent's code
(GADM's GID_1, OCHA's ADM1_PCODE, or just <tt>parent</tt>) and re-apply.</p>"""),
}


def help_button(key, parent=None, size=None):
    """Return a small '?' button that opens the help text for `key`."""
    btn = QPushButton("?")
    btn.setFixedWidth(26)
    btn.setToolTip("Help for this tool")
    title, html = HELP.get(key, ("Help", "<p>No help available.</p>"))
    # table-heavy pages need more room than the plain prose ones
    w, h = size or ((820, 640) if "<table" in html else (560, 520))

    def _show():
        dlg = QDialog(parent)
        dlg.setWindowTitle(title)
        dlg.resize(w, h)
        v = QVBoxLayout(dlg)
        tb = QTextBrowser()
        tb.setHtml(html)
        tb.setOpenExternalLinks(True)
        v.addWidget(tb)
        bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
        bb.rejected.connect(dlg.close)
        bb.accepted.connect(dlg.close)
        v.addWidget(bb)
        dlg.exec()

    btn.clicked.connect(_show)
    return btn
