*! version 1.0.0  12may2026  S. Derya Uysal, Tymon Sloczynski, and Jeffrey M. Wooldridge

* SPDX-License-Identifier: MIT
* Copyright (c) 2026 S. Derya Uysal, Tymon Sloczynski, and Jeffrey M. Wooldridge
* See the LICENSE file in this distribution for full text.

// -----------------------------------------------------------------
// _drlate_parse
// Parse drlate equation specification:
// (y [xvars] [, model]) (d [xvars] [, model]) (z [xvars] [, model])
//
// Returns via sreturn for each equation i=1,2,3:
// s(depvar_i) - dependent variable
// s(indepvars_i) - independent variables (may include factor vars)
// s(model_i) - model type string
// s(eqn_i) - "depvar indepvars" combined
//
// Also: s(if), s(in), s(wt), s(options), s(eqn_n)
//
// Block extraction to handle nested
// parentheses in factor variable notation:
// i.(black south smsa), c.exper##c.exper etc.
// -----------------------------------------------------------------

capture program drop _drlate_parse
program define _drlate_parse, sclass
	version 17

	syntax anything(name=spec) [if] [in] [pw] [, *]

	sreturn clear
	sreturn local if `"`if'"'
	sreturn local in `"`in'"'
	if `"`weight'"' != "" sreturn local wt `"[`weight'`exp']"'
	sreturn local options `"`options'"'

	// -----------------------------------------------------------------
	// Extract three equation blocks by counting parenthesis
	// depth. This correctly handles nested parens such as
	// i.(black south smsa) and c.exper##c.exper inside blocks.
	// -----------------------------------------------------------------
	local remaining `"`spec'"'
	local nblocks 0

	while `"`remaining'"' != "" {
		// Find the next opening paren
		local pos_open = strpos(`"`remaining'"', "(")
		if `pos_open' == 0 {
			// Stop if no more opening parens
			continue, break
		}

		// Advance past the opening paren
		local remaining = substr(`"`remaining'"', `pos_open' + 1, .)

		// Now collect everything until the matching closing paren
		// using depth counting
		local depth 1
		local block ""
		local len = strlen(`"`remaining'"')
		local i 1

		while `i' <= `len' & `depth' > 0 {
			local ch = substr(`"`remaining'"', `i', 1)
			if `"`ch'"' == "(" {
				local depth = `depth' + 1
				local block `"`block'`ch'"'
			}
			else if `"`ch'"' == ")" {
				local depth = `depth' - 1
				if `depth' > 0 {
					local block `"`block'`ch'"'
				}
				// If depth==0 we have found the matching close paren
			}
			else {
				local block `"`block'`ch'"'
			}
			local i = `i' + 1
		}

		// Advance remaining past what we consumed
		local remaining = substr(`"`remaining'"', `i', .)

		local nblocks = `nblocks' + 1
		local rawblock`nblocks' `"`block'"'
	}

	if `nblocks' < 3 {
		di as err "{p}Syntax error: expected " ///
			"(y [xvars] [, model]) " ///
			"(d [xvars] [, model]) " ///
			"(z [xvars] [, model]){p_end}"
		exit 198
	}

	if `nblocks' > 3 {
		di as err "{p}Syntax error: too many equation blocks; " ///
			"expected exactly 3{p_end}"
		exit 198
	}

	// -----------------------------------------------------------------
	// Parse each raw block into depvar, indepvars, model
	// -----------------------------------------------------------------
	local default_1 linear
	local default_2 logit
	local default_3 logit

	forvalues i = 1/3 {
		local raw `"`rawblock`i''"'

		// Split on comma to get varlist part vs model option
		// Use strpos to find first comma
		local pos_comma = strpos(`"`raw'"', ",")

		if `pos_comma' > 0 {
			local varpart = substr(`"`raw'"', 1, `pos_comma' - 1)
			local modelpart = substr(`"`raw'"', `pos_comma' + 1, .)
			local model : list retokenize modelpart
		}
		else {
			local varpart `"`raw'"'
			local model "`default_`i''"
		}

		// Validate model type
		if `i' == 1 {
			if !inlist("`model'", "linear", "logit", "poisson") {
				di as err "{p}Invalid model for outcome equation: " ///
					"{bf:`model'}. " ///
					"Allowed: linear, logit, poisson.{p_end}"
				exit 198
			}
		}
		else if `i' == 2 {
			if !inlist("`model'", "linear", "logit", "poisson") {
				di as err "{p}Invalid model for treatment equation: " ///
					"{bf:`model'}. " ///
					"Allowed: linear, logit, poisson.{p_end}"
				exit 198
			}
		}
		else if `i' == 3 {
			if !inlist("`model'", "logit", "ipt", "cbps") {
				di as err "{p}Invalid model for instrument equation: " ///
					"{bf:`model'}. " ///
					"Allowed: logit, ipt, cbps.{p_end}"
				exit 198
			}
		}

		// Trim leading/trailing whitespace from varpart
		local varpart : list retokenize varpart

		// Split varpart into depvar (first token) and indepvars (rest)
		// using gettoken which handles factor variable tokens correctly
		gettoken depvar indepvars : varpart, bind
		local indepvars : list retokenize indepvars

		if "`depvar'" == "" {
			di as err "{p}Syntax error in equation `i': " ///
				"no dependent variable found{p_end}"
			exit 198
		}

		sreturn local depvar_`i' "`depvar'"
		sreturn local indepvars_`i' `"`indepvars'"'
		sreturn local model_`i' "`model'"
		sreturn local eqn_`i' `"`depvar' `indepvars'"'
	}

	sreturn local eqn_n 3
end