// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

#include "ReportPath.hh"

#include <algorithm>            // reverse
#include <string>
#include <string_view>

#include "ArcDelayCalc.hh"
#include "CheckMaxSkews.hh"
#include "CheckMinPeriods.hh"
#include "CheckMinPulseWidths.hh"
#include "ClkInfo.hh"
#include "ContainerHelpers.hh"
#include "Error.hh"
#include "ExceptionPath.hh"
#include "Format.hh"
#include "Fuzzy.hh"
#include "Genclks.hh"
#include "Graph.hh"
#include "GraphDelayCalc.hh"
#include "InputDrive.hh"
#include "Latches.hh"
#include "Liberty.hh"
#include "Mode.hh"
#include "Network.hh"
#include "Parasitics.hh"
#include "Path.hh"
#include "PathExpanded.hh"
#include "PathGroup.hh"
#include "PortDelay.hh"
#include "PortDirection.hh"
#include "Report.hh"
#include "Scene.hh"
#include "Sdc.hh"
#include "Search.hh"
#include "StringUtil.hh"
#include "Tag.hh"
#include "TimingArc.hh"
#include "TimingRole.hh"
#include "Transition.hh"
#include "Units.hh"
#include "Variables.hh"

namespace sta {

static void
hierPinsAbove(const Net *net,
              const Network *network,
              PinSeq &pins_above);
static void
hierPinsAbove(const Pin *pin,
              const Network *network,
              PinSeq &pins_above);

static PinSeq
hierPinsThruEdge(const Edge *edge,
                 const Network *network,
                 const Graph *graph);

ReportField::ReportField(std::string_view name,
                         std::string_view name_abrev,
                         std::string_view title,
                         size_t width,
                         bool left_justify,
                         Unit *unit,
                         ReportFieldGetValue get_value) :
  name_(name),
  name_abrev_(name_abrev),
  title_(title),
  left_justify_(left_justify),
  unit_(unit),
  get_value_(get_value)
{
  setWidth(width);
}

void
ReportField::setProperties(std::string_view title,
                           size_t width,
                           bool left_justify)
{
  title_ = std::string(title);
  left_justify_ = left_justify;
  setWidth(width);
}

void
ReportField::setWidth(size_t width)
{
  width_ = width;
  blank_.assign(width_, ' ');
}

void
ReportField::setEnabled(bool enabled)
{
  enabled_ = enabled;
}

std::string
ReportField::value(const Path *path,
                   const StaState *sta) const
{
  return get_value_(path, sta);
}

////////////////////////////////////////////////////////////////

ReportPath::ReportPath(StaState *sta) :
  StaState(sta)
{
  makeFields();
  setReportFields({"incr", "total", "edge", "description"});
  setDigits(2);
}

ReportPath::~ReportPath()
{
  deleteContents(fields_);
}

void
ReportPath::makeFields()
{
  // The order corresponds to the default field order.
  field_fanout_ = makeField("fanout", "fanout", "Fanout", 6, false, nullptr);
  field_capacitance_ = makeField("capacitance", "cap", "Cap", 6, false,
                                 units_->capacitanceUnit());
  field_slew_ = makeField("slew", "slew", "Slew", 6, false, units_->timeUnit());
  field_incr_ = makeField("incr", "incr", "Delay", 6, false, units_->timeUnit());
  field_variation_ = makeField("variation", "var", "Variation", 6, false,
                               units_->timeUnit());
  field_total_ = makeField("total", "total", "Time", 6, false, units_->timeUnit());
  field_edge_ = makeField("edge", "edge", "", 1, false, nullptr);
  field_case_ = makeField("case", "case", "case", 11, false, nullptr);
  field_description_ = makeField("description", "desc", "Description", 36, true, nullptr);
  field_src_attr_ = makeField("src_attr", "src", "Src Attr", 40, true, nullptr);
}

ReportField *
ReportPath::makeField(std::string_view name,
                      std::string_view name_abrev,
                      std::string_view title,
                      size_t width,
                      bool left_justify,
                      Unit *unit)
{
  return makeField(name, name_abrev, title, width, left_justify, unit, nullptr);
}

ReportField *
ReportPath::makeField(std::string_view name,
                      std::string_view name_abrev,
                      std::string_view title,
                      size_t width,
                      bool left_justify,
                      Unit *unit,
                      ReportFieldGetValue get_value)
{
  ReportField *field = new ReportField(name, name_abrev, title, width, left_justify,
                                       unit, get_value);
  fields_.push_back(field);
  field_map_[std::string(name)] = field;
  return field;
}

ReportField *
ReportPath::findField(std::string_view name)
{
  return findKey(field_map_, std::string(name));
}

ReportField *
ReportPath::findFieldAbrev(std::string_view name)
{
  for (ReportField *field : fields_) {
    const std::string &name_abrev = field->nameAbrev();
    if (name.substr(0, name_abrev.size()) == name_abrev)
      return field;
  }
  return nullptr;
}

void
ReportPath::setReportFieldOrder(const StringSeq &field_names)
{
  // Disable all fields.
  for (ReportField *field : fields_)
    field->setEnabled(false);

  ReportFieldSeq next_fields;
  for (const std::string &field_name : field_names) {
    ReportField *field = findField(field_name);
    if (field) {
      next_fields.push_back(field);
      field->setEnabled(true);
    }
  }
  // Push remaining disabled fields on the end.
  for (ReportField *field : fields_) {
    if (!field->enabled())
      next_fields.push_back(field);
  }

  fields_ = next_fields;
}

void
ReportPath::setReportFields(const StringSeq &fields)
{
  for (ReportField *field : fields_)
    field->setEnabled(false);
  field_incr_->setEnabled(true);
  field_total_->setEnabled(true);
  field_description_->setEnabled(true);
  field_edge_->setEnabled(true);
  // These are not real fields; they are flags.
  report_input_pin_ = false;
  report_hier_pins_ = false;
  report_net_ = false;

  for (const std::string &field_name : fields) {
    if (field_name == "input_pin")
      report_input_pin_ = true;
    else if (field_name == "hierarchical_pin")
      report_hier_pins_ = true;
    else if (field_name == "net")
      report_net_ = true;
    else {
      ReportField *field = findField(field_name);
      if (field)
        field->setEnabled(true);
      else
        report_->warn(2720, "unknown path reporting field {}.", field_name);
    }
  }
}

void
ReportPath::setPathFormat(ReportPathFormat format)
{
  format_ = format;  
}

void
ReportPath::setNoSplit(bool no_split)
{
  no_split_ = no_split;
}

void
ReportPath::setDigits(int digits)
{
  digits_ = digits;
  minus_zero_ = sta::formatRuntime("-{:.{}f}", 0.0, digits_);
  plus_zero_  = sta::formatRuntime("{:.{}f}", 0.0, digits_);

  // Numeric field width expands with digits.
  int field_width = digits + field_width_extra_;
  field_capacitance_->setWidth(field_width);
  field_slew_->setWidth(field_width);
  field_variation_->setWidth(field_width);
  field_incr_->setWidth(field_width);
  field_total_->setWidth(field_width);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportPathEnd(const PathEnd *end) const
{
  reportPathEnd(end, nullptr, true);
}

void
ReportPath::reportPathEnd(const PathEnd *end,
                          const PathEnd *prev_end,
                          bool last) const
{
  switch (format_) {
  case ReportPathFormat::full:
  case ReportPathFormat::full_clock:
  case ReportPathFormat::full_clock_expanded:
    end->reportFull(this);
    reportBlankLine();
    reportBlankLine();
    break;
  case ReportPathFormat::shorter:
    end->reportShort(this);
    reportBlankLine();
    reportBlankLine();
    break;
  case ReportPathFormat::endpoint:
    reportEndpointHeader(end, prev_end);
    reportEndLine(end);
    break;
  case ReportPathFormat::summary:
    reportSummaryLine(end);
    break;
  case ReportPathFormat::slack_only:
    reportSlackOnly(end);
    break;
  case ReportPathFormat::json:
    reportJson(end, last);
    break;
  }
}

void
ReportPath::reportPathEnds(const PathEndSeq *ends) const
{
  reportPathEndHeader();
  if (ends && !ends->empty()) {
    const PathEnd *prev_end = nullptr;
    for (size_t i = 0; i < ends->size(); i++) {
      const PathEnd *end = (*ends)[i];
      reportPathEnd(end, prev_end, i == ends->size() - 1);
      prev_end = end;
    }
  }
  else {
    if (format_ != ReportPathFormat::json)
      report_->report("No paths found.");
  }
  reportPathEndFooter();
}

void
ReportPath::reportPathEndHeader() const
{
  switch (format_) {
  case ReportPathFormat::full:
  case ReportPathFormat::full_clock:
  case ReportPathFormat::full_clock_expanded:
  case ReportPathFormat::shorter:
  case ReportPathFormat::endpoint:
    break;
  case ReportPathFormat::summary:
    reportSummaryHeader();
    break;
  case ReportPathFormat::slack_only:
    reportSlackOnlyHeader();
    break;
  case ReportPathFormat::json:
    reportJsonHeader();
    break;
  }
}

void
ReportPath::reportPathEndFooter() const
{
  switch (format_) {
  case ReportPathFormat::full:
  case ReportPathFormat::full_clock:
  case ReportPathFormat::full_clock_expanded:
  case ReportPathFormat::shorter:
    break;
  case ReportPathFormat::endpoint:
  case ReportPathFormat::summary:
  case ReportPathFormat::slack_only:
    reportBlankLine();
    break;
  case ReportPathFormat::json:
    reportJsonFooter();
    break;
  }
}

void
ReportPath::reportEndpointHeader(const PathEnd *end,
                                 const PathEnd *prev_end) const
{
  PathGroup *prev_group = nullptr;
  if (prev_end)
    prev_group = prev_end->pathGroup();
  PathGroup *group = end->pathGroup();
  if (group && group != prev_group) {
    if (prev_group)
      reportBlankLine();
    const char *setup_hold = (end->minMax(this) == MinMax::min())
      ? "min_delay/hold"
      : "max_delay/setup";
    report_->report("{} group {}", setup_hold, group->name());
    reportBlankLine();
    reportEndHeader();
  }
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndUnconstrained *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndUnconstrained *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  reportUnclockedEndpoint(end, "internal pin");
  reportGroup(end);
}

void
ReportPath::reportFull(const PathEndUnconstrained *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
  reportBlankLine();

  reportPath(end, expanded);
  reportLine("data arrival time", end->dataArrivalTimeOffset(this),
             end->pathEarlyLate(this));
  reportDashLine();
  report_->report("(Path is unconstrained)");
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndCheck *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndCheck *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  reportEndpoint(end);
  reportGroup(end);
}

void
ReportPath::reportFull(const PathEndCheck *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
  reportSrcPathArrival(end, expanded);
  reportTgtClk(end);
  reportRequired(end, checkRoleString(end));
  reportSlack(end);
}

std::string
ReportPath::checkRoleString(const PathEnd *end) const
{
  return sta::format("library {} time",
                     end->checkRole(this)->to_string());
}

void
ReportPath::reportEndpoint(const PathEndCheck *end) const
{
  Instance *inst = network_->instance(end->vertex(this)->pin());
  std::string inst_name = cmd_network_->pathName(inst);
  std::string clk_name = tgtClkName(end);
  std::string_view rise_fall = asRisingFalling(end->targetClkEndTrans(this));
  const TimingRole *check_role = end->checkRole(this);
  const TimingRole *check_generic_role = check_role->genericRole();
  if (check_role == TimingRole::recovery()
      || check_role == TimingRole::removal()) {
    std::string reason = sta::format("{} check against {}-edge clock {}",
                                     check_role->to_string(),
                                     rise_fall,
                                     clk_name);
    reportEndpoint(inst_name, reason);
  }
  else if (check_generic_role == TimingRole::setup()
           || check_generic_role == TimingRole::hold()) {
    LibertyCell *cell = network_->libertyCell(inst);
    if (cell->isClockGate()) {
      std::string reason = sta::format("{} clock gating-check end-point clocked by {}",
                                       rise_fall, clk_name);
      reportEndpoint(inst_name, reason);
    }
    else {
      std::string_view reg_desc = clkRegLatchDesc(end);
      std::string reason = sta::format("{} clocked by {}", reg_desc, clk_name);
      reportEndpoint(inst_name, reason);
    }
  }
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndLatchCheck *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndLatchCheck *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  reportEndpoint(end);
  reportGroup(end);
}

void
ReportPath::reportFull(const PathEndLatchCheck *end) const
{
  PathExpanded expanded(end->path(), this);
  const EarlyLate *early_late = end->pathEarlyLate(this);
  reportShort(end, expanded);
  reportBlankLine();

  PathDelay *path_delay = end->pathDelay();
  bool ignore_clk_latency = path_delay && path_delay->ignoreClkLatency();
  if (ignore_clk_latency) {
    // Based on reportSrcPath.
    reportPathHeader();
    reportPath3(end->path(), expanded, false, end->sourceClkOffset(this));
  }
  else
    reportSrcPath(end, expanded);
  reportLine("data arrival time", end->dataArrivalTimeOffset(this), early_late);
  reportBlankLine();

  Required req_time;
  Arrival borrow, adjusted_data_arrival, time_given_to_startpoint;
  end->latchRequired(this, req_time, borrow, adjusted_data_arrival,
                     time_given_to_startpoint);
  // Adjust required to requiredTimeOffset.
  req_time = delaySum(req_time, end->sourceClkOffset(this), this);
  if (path_delay) {
    float delay = path_delay->delay();
    reportLine("max_delay", delay, delay, early_late);
    if (!ignore_clk_latency) {
      if (reportClkPath()
          && isPropagated(end->targetClkPath()))
        reportTgtClk(end, delay);
      else {
        Delay delay1(delay);
        reportCommonClkPessimism(end, delay1);
      }
    }
  }
  else
    reportTgtClk(end);

  if (delayGreaterEqual(borrow, 0.0, this))
    reportLine("time borrowed from endpoint", borrow, req_time, early_late);
  else
    reportLine("time given to endpoint", borrow, req_time, early_late);
  reportLine("data required time", req_time, early_late);
  reportDashLine();
  reportSlack(end);
  if (end->checkGenericRole(this) == TimingRole::setup()
      && !ignore_clk_latency) {
    reportBlankLine();
    reportBorrowing(end, borrow, time_given_to_startpoint);
  }
}

void
ReportPath::reportEndpoint(const PathEndLatchCheck *end) const
{
  Instance *inst = network_->instance(end->vertex(this)->pin());
  std::string clk_name = tgtClkName(end);
  std::string_view reg_desc = latchDesc(end);
  std::string reason = sta::format("{} clocked by {}", reg_desc, clk_name);
  std::string inst_name = cmd_network_->pathName(inst);
  reportEndpoint(inst_name, reason);
}

std::string_view
ReportPath::latchDesc(const PathEndLatchCheck *end) const
{
  TimingArc *check_arc = end->checkArc();
  const RiseFall *en_rf = check_arc->fromEdge()->asRiseFall()->opposite();
  return latchDesc(en_rf);
}

void
ReportPath::reportBorrowing(const PathEndLatchCheck *end,
                            Arrival &borrow,
                            Arrival &time_given_to_startpoint) const
{
  Delay open_latency, latency_diff, max_borrow;
  float nom_pulse_width, open_uncertainty;
  Crpr open_crpr, crpr_diff;
  bool borrow_limit_exists;
  const EarlyLate *early_late = EarlyLate::late();
  end->latchBorrowInfo(this, nom_pulse_width, open_latency, latency_diff,
                       open_uncertainty, open_crpr, crpr_diff,
                       max_borrow, borrow_limit_exists);
  report_->report("Time Borrowing Information");
  reportDashLineTotal();
  if (borrow_limit_exists)
    reportLineTotal("user max time borrow", max_borrow, early_late);
  else {
    std::string tgt_clk_name = tgtClkName(end);
    Arrival tgt_clk_width = end->targetClkWidth(this);
    const Path *tgt_clk_path = end->targetClkPath();
    if (tgt_clk_path->clkInfo(search_)->isPropagated()) {
      std::string width_msg = sta::format("{} nominal pulse width", tgt_clk_name);
      reportLineTotal(width_msg, nom_pulse_width, early_late);
      if (!delayZero(latency_diff, this))
        reportLineTotalMinus("clock latency difference", latency_diff, early_late);
    }
    else {
      std::string width_msg = sta::format("{} pulse width", tgt_clk_name);
      reportLineTotal(width_msg, tgt_clk_width, early_late);
    }
    ArcDelay margin = end->margin(this);
    reportLineTotalMinus("library setup time", margin, early_late);
    reportDashLineTotal();
    if (!delayZero(crpr_diff, this))
      reportLineTotalMinus("CRPR difference", crpr_diff, early_late);
    reportLineTotal("max time borrow", max_borrow, early_late);
  }
  if (delayGreater(borrow, delay_zero, this)
      && (!fuzzyZero(open_uncertainty)
          || !delayZero(open_crpr, this))) {
    reportDashLineTotal();
    reportLineTotal("actual time borrow", borrow, early_late);
    if (!fuzzyZero(open_uncertainty))
      reportLineTotal("open edge uncertainty", open_uncertainty, early_late);
    if (!delayZero(open_crpr, this))
      reportLineTotal("open edge CRPR", open_crpr, early_late);
    reportDashLineTotal();
    reportLineTotal("time given to startpoint", time_given_to_startpoint, early_late);
  }
  else
    reportLineTotal("actual time borrow", borrow, early_late);
  reportDashLineTotal();
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndPathDelay *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndPathDelay *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  if (end->targetClk(this))
    reportEndpoint(end);
  else
    reportUnclockedEndpoint(end, "internal path endpoint");
  reportGroup(end);
}

void
ReportPath::reportEndpoint(const PathEndPathDelay *end) const
{
  if (end->hasOutputDelay())
    reportEndpointOutputDelay(end);
  else {
    Instance *inst = network_->instance(end->vertex(this)->pin());
    std::string reason = sta::format("{} clocked by {}",
                                     clkRegLatchDesc(end),
                                     tgtClkName(end));
    reportEndpoint(cmd_network_->pathName(inst), reason);
  }
}

void
ReportPath::reportFull(const PathEndPathDelay *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
  const EarlyLate *early_late = end->pathEarlyLate(this);

  // Based on reportSrcPathArrival.
  reportBlankLine();
  PathDelay *path_delay = end->pathDelay();
  if (end->ignoreClkLatency(this)) {
    // Based on reportSrcPath.
    reportPathHeader();
    reportPath3(end->path(), expanded, false, end->sourceClkOffset(this));
  }
  else
    reportSrcPath(end, expanded);
  reportLine("data arrival time", end->dataArrivalTimeOffset(this), early_late);
  reportBlankLine();

  ArcDelay margin = end->margin(this);
  const MinMax *min_max = path_delay->minMax()->asMinMax();
  if (min_max == MinMax::max())
    margin = delayDiff(delay_zero, margin, this);

  std::string delay_msg = min_max->to_string() + "_delay";
  float delay = path_delay->delay();
  reportLine(delay_msg, delay, delay, early_late);
  if (!path_delay->ignoreClkLatency()) {
    const Clock *tgt_clk = end->targetClk(this);
    if (tgt_clk) {
      const Path *tgt_clk_path = end->targetClkPath();
      if (reportClkPath()
          && isPropagated(tgt_clk_path, tgt_clk))
        reportTgtClk(end, delay, 0.0, true);
      else {
        Arrival tgt_clk_delay = end->targetClkDelay(this);
        Arrival tgt_clk_arrival = delaySum(tgt_clk_delay, delay, this);
        if (!delayZero(tgt_clk_delay, this))
          reportLine(clkNetworkDelayIdealProp(isPropagated(tgt_clk_path)),
                     tgt_clk_delay, tgt_clk_arrival, early_late);
        reportClkUncertainty(end, tgt_clk_arrival);
        reportCommonClkPessimism(end, tgt_clk_arrival);
      }
    }
  }
  if (end->pathDelayMarginIsExternal())
    reportRequired(end, "output external delay");
  else
    reportRequired(end, checkRoleString(end));
  reportSlack(end);
}

bool
ReportPath::isPropagated(const Path *clk_path) const
{
  return clk_path->clkInfo(search_)->isPropagated();
}

bool
ReportPath::isPropagated(const Path *clk_path,
                         const Clock *clk) const
{
  if (clk_path)
    return clk_path->clkInfo(search_)->isPropagated();
  else
    return clk->isPropagated();
}

std::string_view
ReportPath::clkNetworkDelayIdealProp(bool is_prop) const
{
  if (is_prop)
    return "clock network delay (propagated)";
  else
    return "clock network delay (ideal)";
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndOutputDelay *end)  const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndOutputDelay *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  reportEndpoint(end);
  reportGroup(end);
}

void
ReportPath::reportFull(const PathEndOutputDelay *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
  reportSrcPathArrival(end, expanded);
  reportTgtClk(end);
  reportRequired(end, "output external delay");
  reportSlack(end);
}

void
ReportPath::reportEndpoint(const PathEndOutputDelay *end) const
{
  reportEndpointOutputDelay(end);
}

void
ReportPath::reportEndpointOutputDelay(const PathEndClkConstrained *end) const
{
  Vertex *vertex = end->vertex(this);
  Pin *pin = vertex->pin();
  std::string pin_name = cmd_network_->pathName(pin);
  const Clock *tgt_clk = end->targetClk(this);
  if (network_->isTopLevelPort(pin)) {
    // Pin direction is "output" even for bidirects.
    if (tgt_clk) {
      std::string reason = sta::format("output port clocked by {}",
                                       tgtClkName(end));
      reportEndpoint(pin_name, reason);
    }
    else
      reportEndpoint(pin_name, "output port");
  }
  else {
    if (tgt_clk) {
      std::string reason = sta::format("internal path endpoint clocked by {}",
                                       tgtClkName(end));

      reportEndpoint(pin_name, reason);
    }
    else
      reportEndpoint(pin_name, "internal path endpoint");
  }
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndGatedClock *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndGatedClock *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  reportEndpoint(end);
  reportGroup(end);
}

void
ReportPath::reportFull(const PathEndGatedClock *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
  reportSrcPathArrival(end, expanded);
  reportTgtClk(end);
  reportRequired(end, checkRoleReason(end));
  reportSlack(end);
}

void
ReportPath::reportEndpoint(const PathEndGatedClock *end) const
{
  Instance *inst = network_->instance(end->vertex(this)->pin());
  const std::string inst_name = cmd_network_->pathName(inst);
  const RiseFall *clk_end_rf = end->targetClkEndTrans(this);
  const RiseFall *clk_rf = (end->minMax(this) == MinMax::max())
    ? clk_end_rf
    : clk_end_rf->opposite();
  // Note that target clock transition is ignored.
  std::string reason = sta::format("{} clock gating-check end-point clocked by {}",
                                   asRisingFalling(clk_rf),
                                   tgtClkName(end));
  reportEndpoint(inst_name, reason);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportShort(const PathEndDataCheck *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
}

void
ReportPath::reportShort(const PathEndDataCheck *end,
                        const PathExpanded &expanded) const
{
  reportStartpoint(end, expanded);
  reportEndpoint(end);
  reportGroup(end);

}

void
ReportPath::reportFull(const PathEndDataCheck *end) const
{
  PathExpanded expanded(end->path(), this);
  reportShort(end, expanded);
  reportSrcPathArrival(end, expanded);

  // Data check target clock path reporting resembles 
  // both source (reportSrcPath) and target (reportTgtClk) clocks. 
  // It is like a source because it can be a non-clock path.
  // It is like a target because crpr and uncertainty are reported.
  // It is always propagated, even if the clock is ideal.
  reportTgtClk(end, 0.0, true);
  const Path *data_clk_path = end->dataClkPath();
  if (!data_clk_path->isClock(this)) {
    // Report the path from the clk network to the data check.
    PathExpanded clk_expanded(data_clk_path, this);
    float src_offset = end->sourceClkOffset(this);
    Delay clk_delay = end->targetClkDelay(this);
    const MinMax *min_max = data_clk_path->minMax(this);
    Arrival clk_arrival = end->targetClkArrival(this);
    const ClockEdge *tgt_clk_edge = end->targetClkEdge(this);
    float prev = delayAsFloat(clk_arrival, min_max, this) + src_offset;
    float offset = prev - delayAsFloat(clk_delay, min_max, this) - tgt_clk_edge->time();
    // Delay to startpoint is already included.
    reportPath6(data_clk_path, clk_expanded, clk_expanded.startIndex(),
                true, false, prev, offset);
  }
  reportRequired(end, checkRoleReason(end));
  reportSlack(end);
}

void
ReportPath::reportEndpoint(const PathEndDataCheck *end) const
{
  Instance *inst = network_->instance(end->vertex(this)->pin());
  const std::string inst_name = cmd_network_->pathName(inst);
  std::string reason = sta::format("{} edge-triggered data to data check clocked by {}",
                                   asRisingFalling(end->dataClkPath()->transition(this)),
                                   end->targetClk(this)->name());
  reportEndpoint(inst_name, reason);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportEndHeader() const
{
  std::string line;
  // Line one.
  reportDescription("", line);
  line += ' ';
  reportField("Required", field_total_, line);
  line += ' ';
  reportField("Actual", field_total_, line);
  report_->reportLine(line);

  // Line two.
  line.clear();
  reportDescription("Endpoint", line);
  line += ' ';
  reportField("Delay", field_total_, line);
  line += ' ';
  reportField("Delay", field_total_, line);
  line += ' ';
  reportField("Slack", field_total_, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() + field_total_->width() * 3 + 3);
}

void
ReportPath::reportEndLine(const PathEnd *end) const
{
  std::string line;
  std::string endpoint = pathEndpoint(end);
  reportDescription(endpoint, line);
  const EarlyLate *early_late = end->pathEarlyLate(this);
  reportSpaceFieldDelay(end->requiredTimeOffset(this), early_late, line);
  reportSpaceFieldDelay(end->dataArrivalTimeOffset(this), early_late, line);
  reportSpaceSlack(end, line);
  report_->reportLine(line);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportSummaryHeader() const
{
  std::string line;
  reportDescription("Startpoint", line);
  line += ' ';
  reportDescription("Endpoint", line);
  line += ' ';
  reportField("Slack", field_total_, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() * 2 + field_total_->width() + 1);
}

void
ReportPath::reportSummaryLine(const PathEnd *end) const
{
  std::string line;
  PathExpanded expanded(end->path(), this);
  const EarlyLate *early_late = end->pathEarlyLate(this);
  std::string startpoint = pathStartpoint(end, expanded);
  reportDescription(startpoint, line);
  line += ' ';
  std::string endpoint = pathEndpoint(end);
  reportDescription(endpoint, line);
  if (end->isUnconstrained())
    reportSpaceFieldDelay(end->dataArrivalTimeOffset(this), early_late, line);
  else
    reportSpaceFieldDelay(end->slack(this), EarlyLate::early(), line);
  report_->reportLine(line);
}

std::string
ReportPath::pathStartpoint(const PathEnd *end,
                           const PathExpanded &expanded) const
{
  const Path *start = expanded.startPath();
  Pin *pin = start->pin(graph_);
  std::string pin_name = cmd_network_->pathName(pin);
  if (network_->isTopLevelPort(pin)) {
    PortDirection *dir = network_->direction(pin);
    return sta::format("{} ({})", pin_name, dir->name());
  }
  else {
    Instance *inst = network_->instance(end->vertex(this)->pin());
    std::string cell_name = cmd_network_->name(network_->cell(inst));
    return sta::format("{} ({})", pin_name, cell_name);
  }
}

std::string
ReportPath::pathEndpoint(const PathEnd *end) const
{
  Pin *pin = end->vertex(this)->pin();
  std::string pin_name = cmd_network_->pathName(pin);
  if (network_->isTopLevelPort(pin)) {
    PortDirection *dir = network_->direction(pin);
    return sta::format("{} ({})", pin_name, dir->name());
  }
  else {
    Instance *inst = network_->instance(end->vertex(this)->pin());
    std::string cell_name = cmd_network_->name(network_->cell(inst));
    return sta::format("{} ({})", pin_name, cell_name);
  }
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportJsonHeader() const
{
  report_->report("{{\"checks\": [");
}

void
ReportPath::reportJsonFooter() const
{
  report_->report("]");
  report_->report("}}");
}

void
ReportPath::reportJson(const PathEnd *end,
                       bool last) const
{
  std::string result;
  result += "{\n";
  result += "  \"type\": \"";
  result += end->typeName();
  result += "\",\n";

  result += "  \"path_group\": \"";
  result += end->pathGroup()->name();
  result += "\",\n";

  result += "  \"path_type\": \"";
  result += end->minMax(this)->to_string();
  result += "\",\n";  

  PathExpanded expanded(end->path(), this);
  const Pin *startpoint = expanded.startPath()->vertex(this)->pin();
  const Pin *endpoint = expanded.endPath()->vertex(this)->pin();
  result += sta::format("  \"startpoint\": \"{}\",\n",
               sdc_network_->pathName(startpoint));
  result += sta::format("  \"endpoint\": \"{}\",\n",
               sdc_network_->pathName(endpoint));

  const ClockEdge *src_clk_edge = end->sourceClkEdge(this);
  const Path *src_clk_path = expanded.clkPath();
  const Path *tgt_clk_path = end->targetClkPath();
  if (src_clk_edge) {
    result += sta::format("  \"source_clock\": \"{}\",\n",
                 src_clk_edge->clock()->name());
    result += sta::format("  \"source_clock_edge\": \"{}\",\n",
                 src_clk_edge->transition()->name());
  }
  if (src_clk_path)
    reportJson(src_clk_path, "source_clock_path", 2, true, result);
  reportJson(expanded, "source_path", 2, !end->isUnconstrained(), result);

  const ClockEdge *tgt_clk_edge = end->targetClkEdge(this);
  if (tgt_clk_edge) {
    result += sta::format("  \"target_clock\": \"{}\",\n",
                 tgt_clk_edge->clock()->name());
    result += sta::format("  \"target_clock_edge\": \"{}\",\n",
                 tgt_clk_edge->transition()->name());
  }
  if (tgt_clk_path)
    reportJson(end->targetClkPath(), "target_clock_path", 2, true, result);

  if (end->checkRole(this)) {
    result += sta::format("  \"data_arrival_time\": {:.3e},\n",
                 delayAsFloat(end->dataArrivalTimeOffset(this)));

    const MultiCyclePath *mcp = end->multiCyclePath();
    if (mcp)
      result += sta::format("  \"multi_cycle_path\": {},\n",
                   mcp->pathMultiplier());

    PathDelay *path_delay = end->pathDelay();
    if (path_delay)
      result += sta::format("  \"path_delay\": {:.3e},\n",
                   path_delay->delay());

    result += sta::format("  \"crpr\": {:.3e},\n",
                 delayAsFloat(end->checkCrpr(this)));
    result += sta::format("  \"margin\": {:.3e},\n",
                 delayAsFloat(end->margin(this)));
    result += sta::format("  \"required_time\": {:.3e},\n",
                 delayAsFloat(end->requiredTimeOffset(this)));
    result += sta::format("  \"slack\": {:.3e}\n",
                 delayAsFloat(end->slack(this)));
  }
  result += "}";
  if (!last)
    result += ",";
  report_->reportLine(result);
}

void
ReportPath::reportJson(const Path *path) const
{
  std::string result;
  result += "{\n";
  reportJson(path, "path", 0, false, result);
  result += "}\n";
  report_->reportLine(result);
}

void
ReportPath::reportJson(const Path *path,
                       std::string_view path_name,
                       int indent,
                       bool trailing_comma,
                       std::string &result) const
{
  PathExpanded expanded(path, this);
  reportJson(expanded, path_name, indent, trailing_comma, result);
}

void
ReportPath::reportJson(const PathExpanded &expanded,
                       std::string_view path_name,
                       int indent,
                       bool trailing_comma,
                       std::string &result) const
{
  result += sta::format("{:>{}}\"{}\": [\n", "", indent, path_name);
  for (size_t i = expanded.startIndex(); i < expanded.size(); i++) {
    const Path *path = expanded.path(i);
    const Pin *pin = path->vertex(this)->pin();
    const Net *net = network_->net(pin);
    const Instance *inst = network_->instance(pin);
    const RiseFall *rf = path->transition(this);
    const Scene *scene = path->scene(this);
    const MinMax *min_max = path->minMax(this);
    bool is_driver = network_->isDriver(pin);

    result += sta::format("{:>{}}  {{\n", "", indent);

    if (inst) {
      result += sta::format("{:>{}}    \"instance\": \"{}\",\n",
                            "", indent,
                            sdc_network_->pathName(inst));
      Cell *cell = network_->cell(inst);
      if (cell)
        result += sta::format("{:>{}}    \"cell\": \"{}\",\n",
                              "", indent,
                              sdc_network_->name(cell));
      result += sta::format("{:>{}}    \"verilog_src\": \"{}\",\n",
                            "", indent,
                            sdc_network_->getAttribute(inst, "src"));
    }

    result += sta::format("{:>{}}    \"pin\": \"{}\",\n",
                          "", indent,
                          sdc_network_->pathName(pin));

    if (net) {
      result += sta::format("{:>{}}    \"net\": \"{}\",\n",
                            "", indent,
                            sdc_network_->pathName(net));
    }

    PinSeq pins_above;
    hierPinsAbove(pin, network_, pins_above);
    if (!pins_above.empty()) {
      result += sta::format("{:>{}}    \"hier_pins\": [\n", "", indent);
      for (const Pin *hpin : pins_above) {
        result += sta::format("{:>{}}      \"{}\"{}\n",
                              "", indent,
                              sdc_network_->pathName(hpin),
                              (hpin != pins_above.back()) ? "," : "");
      }
      result += sta::format("{:>{}}    ],\n", "", indent);
    }

    double x, y;
    bool exists;
    network_->location(pin, x, y, exists);
    if (exists) {
      result += sta::format("{:>{}}    \"x\": {:.9f},\n", "", indent, x);
      result += sta::format("{:>{}}    \"y\": {:.9f},\n", "", indent, y);
    }

    result += sta::format("{:>{}}    \"arrival\": {:.3e},\n",
                          "", indent,
                          delayAsFloat(path->arrival()));
    if (is_driver)
      result += sta::format("{:>{}}    \"capacitance\": {:.3e},\n",
                            "", indent,
                            graph_delay_calc_->loadCap(pin, rf, scene, min_max));
    result += sta::format("{:>{}}    \"slew\": {:.3e}\n",
                          "", indent,
                          delayAsFloat(path->slew(this)));
    result += sta::format("{:>{}}  }}{}\n",
                          "", indent,
                          (i < expanded.size() - 1) ? "," : "");
  }
  result += sta::format("{:>{}}]{}\n",
                        "", indent,
                        trailing_comma ? "," : "");
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportSlackOnlyHeader() const
{
  std::string line;
  reportDescription("Group", line);
  line += ' ';
  reportField("Slack", field_total_, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() + field_total_->width() + 1);
}

void
ReportPath::reportSlackOnly(const PathEnd *end) const
{
  std::string line;
  const EarlyLate *early_late = end->pathEarlyLate(this);
  reportDescription(end->pathGroup()->name(), line);
  if (end->isUnconstrained())
    reportSpaceFieldDelay(end->dataArrivalTimeOffset(this), early_late, line);
  else
    reportSpaceFieldDelay(end->slack(this), early_late, line);
  report_->reportLine(line);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportMpwCheck(const MinPulseWidthCheck &check,
                           bool verbose) const
{
  if (verbose) {
    reportVerbose(check);
    reportBlankLine();
  }
  else {
    reportMpwHeaderShort();
    reportShort(check);
  }
  reportBlankLine();
}

void
ReportPath::reportMpwChecks(const MinPulseWidthCheckSeq &checks,
                            bool verbose) const
{
  if (!checks.empty()) {
    if (verbose) {
      for (const MinPulseWidthCheck &check : checks) {
        reportVerbose(check);
        reportBlankLine();
      }
    }
    else {
      reportMpwHeaderShort();
      for (const MinPulseWidthCheck &check : checks)
        reportShort(check);
    }
    reportBlankLine();
  }
}

void
ReportPath::reportMpwHeaderShort() const
{
  std::string line;
  reportDescription("", line);
  line += ' ';
  reportField("Required", field_total_, line);
  line += ' ';
  reportField("Actual", field_total_, line);
  report_->reportLine(line);

  line.clear();
  reportDescription("Pin", line);
  line += ' ';
  reportField("Width", field_total_, line);
  line += ' ';
  reportField("Width", field_total_, line);
  line += ' ';
  reportField("Slack", field_total_, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() + field_total_->width() * 3 + 3);
}

void
ReportPath::reportShort(const MinPulseWidthCheck &check) const
{
  std::string line;
  std::string what = sta::format("{} ({})",
                                 cmd_network_->pathName(check.pin(this)),
                                 mpwCheckHiLow(check));
  reportDescription(what, line);
  reportSpaceFieldTime(check.minWidth(this), line);
  reportSpaceFieldDelay(check.width(this), EarlyLate::late(), line);
  reportSpaceSlack(check.slack(this), line);
  report_->reportLine(line);
}

void
ReportPath::reportVerbose(const MinPulseWidthCheck &check) const
{
  std::string pin_name = cmd_network_->pathName(check.pin(this));
  std::string line = "Pin: " + pin_name;
  report_->reportLine(line);

  report_->report("Check: sequential_clock_pulse_width");
  reportBlankLine();
  reportPathHeader();

  const EarlyLate *open_el = EarlyLate::late();
  const ClockEdge *open_clk_edge = check.openClkEdge(this);
  const Clock *open_clk = open_clk_edge->clock();
  float open_clk_time = open_clk_edge->time();
  std::string open_clk_msg = sta::format("clock {} ({} edge)",
                                         open_clk->name(),
                                         asRiseFall(open_clk_edge->transition()));
  reportLine(open_clk_msg, open_clk_time, open_clk_time, open_el);

  Arrival open_arrival = check.openArrival(this);
  bool is_prop = isPropagated(check.openPath());
  std::string_view clk_ideal_prop = clkNetworkDelayIdealProp(is_prop);
  reportLine(clk_ideal_prop, check.openDelay(this), open_arrival, open_el);
  reportLine(pin_name, delay_zero, open_arrival, open_el);
  reportLine("open edge arrival time", open_arrival, open_el);
  reportBlankLine();

  const EarlyLate *close_el = EarlyLate::late();
  const ClockEdge *close_clk_edge = check.closeClkEdge(this);
  const Clock *close_clk = close_clk_edge->clock();
  float close_offset = check.closeOffset(this);
  float close_clk_time = close_clk_edge->time() + close_offset;
  std::string close_clk_msg = sta::format("clock {} ({} edge)",
                                          close_clk->name(),
                                          asRiseFall(close_clk_edge->transition()));
  reportLine(close_clk_msg, close_clk_time, close_clk_time, close_el);

  Arrival close_arrival = delaySum(check.closeArrival(this), close_offset, this);
  reportLine(clk_ideal_prop, check.closeDelay(this), close_arrival, close_el);

  reportLine(pin_name, delay_zero, close_arrival, close_el);

  if (variables_->crprEnabled()) {
    Crpr pessimism = check.checkCrpr(this);
    close_arrival = delaySum(close_arrival, pessimism, this);
    reportLine("clock reconvergence pessimism", pessimism, close_arrival, close_el);
  }
  reportLine("close edge arrival time", close_arrival, close_el);
  reportDashLine();

  float min_width = check.minWidth(this);
  std::string rpw_msg = sta::format("required pulse width ({})",
                                    mpwCheckHiLow(check));
  reportLine(rpw_msg, min_width, EarlyLate::early());
  reportLine("actual pulse width", check.width(this), EarlyLate::early());
  reportDashLine();
  reportSlack(check.slack(this));
}

std::string_view
ReportPath::mpwCheckHiLow(const MinPulseWidthCheck &check) const
{
  if (check.openTransition(this) == RiseFall::rise())
    return "high";
  else
    return "low";
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportCheck(const MinPeriodCheck &check,
                        bool verbose) const
{
  if (verbose) {
    reportVerbose(check);
    reportBlankLine();
  }
  else {
    reportPeriodHeaderShort();
    reportShort(check);
  }
  reportBlankLine();
}

void
ReportPath::reportChecks(const MinPeriodCheckSeq &checks,
                         bool verbose) const
{
  if (!checks.empty()) {
    if (verbose) {
      for (const MinPeriodCheck &check : checks) {
        reportVerbose(check);
        reportBlankLine();
      }
    }
    else {
      reportPeriodHeaderShort();
      for (const MinPeriodCheck &check : checks)
        reportShort(check);
    }
    reportBlankLine();
  }
}

void
ReportPath::reportPeriodHeaderShort() const
{
  std::string line;
  reportDescription("", line);
  line += ' ';
  reportField("", field_total_, line);
  line += ' ';
  reportField("Min", field_total_, line);
  line += ' ';
  reportField("", field_total_, line);
  report_->reportLine(line);

  line.clear();
  reportDescription("Pin", line);
  line += ' ';
  reportField("Period", field_total_, line);
  line += ' ';
  reportField("Period", field_total_, line);
  line += ' ';
  reportField("Slack", field_total_, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() + field_total_->width() * 3 + 3);
}

void
ReportPath::reportShort(const MinPeriodCheck &check) const
{
  std::string line;
  const std::string pin_name = cmd_network_->pathName(check.pin());
  reportDescription(pin_name, line);
  reportSpaceFieldDelay(check.period(), EarlyLate::early(), line);
  reportSpaceFieldDelay(check.minPeriod(this), EarlyLate::early(), line);
  reportSpaceSlack(check.slack(this), line);
  report_->reportLine(line);
}

void
ReportPath::reportVerbose(const MinPeriodCheck &check) const
{
  std::string line = "Pin: " + cmd_network_->pathName(check.pin());
  report_->reportLine(line);

  reportLine("period", check.period(), EarlyLate::early());
  reportLine("min period", -check.minPeriod(this), EarlyLate::early());
  reportDashLine();

  reportSlack(check.slack(this));
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportChecks(const MaxSkewCheckSeq &checks,
                         bool verbose) const
{
  if (!checks.empty()) {
    if (verbose) {
      for (const MaxSkewCheck &check : checks)
        reportVerbose(check);
    }
    else {
      reportMaxSkewHeaderShort();
      for (const MaxSkewCheck &check : checks)
        reportShort(check);
    }
    reportBlankLine();
  }
}

void
ReportPath::reportMaxSkewHeaderShort() const
{
  std::string line;
  reportDescription("", line);
  line += ' ';
  reportField("Required", field_total_, line);
  line += ' ';
  reportField("Actual", field_total_, line);
  line += ' ';
  reportField("", field_total_, line);
  report_->reportLine(line);

  line.clear();
  reportDescription("Pin", line);
  line += ' ';
  reportField("Skew", field_total_, line);
  line += ' ';
  reportField("Skew", field_total_, line);
  line += ' ';
  reportField("Slack", field_total_, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() + field_total_->width() * 3 + 3);
}

void
ReportPath::reportShort(const MaxSkewCheck &check) const
{
  std::string line;
  Pin *clk_pin = check.clkPin(this);
  TimingArc *check_arc = check.checkArc();
  std::string what = sta::format("{} ({}->{})",
                                 network_->pathName(clk_pin),
                                 check_arc->fromEdge()->to_string(),
                                 check_arc->toEdge()->to_string());
  reportDescription(what, line);
  const EarlyLate *early_late = EarlyLate::early();
  reportSpaceFieldDelay(check.maxSkew(this), early_late, line);
  reportSpaceFieldDelay(check.skew(this), early_late, line);
  reportSpaceSlack(check.slack(this), line);
  report_->reportLine(line);
}

void
ReportPath::reportVerbose(const MaxSkewCheck &check) const
{
  std::string line;
  line += "Constrained Pin: ";
  line += cmd_network_->pathName(check.clkPin(this));
  report_->reportLine(line);

  line = "Reference   Pin: ";
  line += cmd_network_->pathName(check.refPin(this));
  report_->reportLine(line);

  line = "Check: max_skew";
  report_->reportLine(line);
  reportBlankLine();

  reportPathHeader();
  reportSkewClkPath("reference pin arrival time", check.refPath());
  reportSkewClkPath("constrained pin arrival time", check.clkPath());

  reportDashLine();
  reportLine("allowable skew", check.maxSkew(this), EarlyLate::early());
  reportLine("actual skew", check.skew(this), EarlyLate::late());
  reportDashLine();
  reportSlack(check.slack(this));
}

// Based on reportTgtClk.
void
ReportPath::reportSkewClkPath(std::string_view arrival_msg,
                              const Path *clk_path) const
{
  const ClockEdge *clk_edge = clk_path->clkEdge(this);
  const Clock *clk = clk_edge->clock();
  const EarlyLate *early_late = clk_path->minMax(this);
  const RiseFall *clk_rf = clk_edge->transition();
  const RiseFall *clk_end_rf = clk_path->transition(this);
  std::string clk_name = clkName(clk, clk_end_rf != clk_rf);
  float clk_time = clk_edge->time();
  const Arrival &clk_arrival = search_->clkPathArrival(clk_path);
  Arrival clk_delay = delayDiff(clk_arrival,
                                clk_time,
                                this);
  const MinMax *min_max = clk_path->minMax(this);
  Vertex *clk_vertex = clk_path->vertex(this);
  reportClkLine(clk, clk_name, clk_end_rf, clk_time, min_max);

  bool is_prop = isPropagated(clk_path);
  if (is_prop && reportClkPath()) {
    const Mode *mode = clk_path->mode(this);
    const Sdc *sdc = mode->sdc();
    const EarlyLate *early_late = TimingRole::skew()->tgtClkEarlyLate();
    if (reportGenClkSrcPath(clk_path, clk, clk_rf, min_max, early_late, sdc))
      reportGenClkSrcAndPath(clk_path, clk, clk_rf, early_late, 0.0, 0.0,
                             false, mode);
    else {
      Arrival insertion, latency;
      PathEnd::checkTgtClkDelay(clk_path, clk_edge, TimingRole::skew(), this,
                                insertion, latency);
      reportClkSrcLatency(insertion, clk_time, early_late);
      PathExpanded clk_expanded(clk_path, this);
      reportPath1(clk_path, clk_expanded, false, 0.0);
    }
  }
  else {
    reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, clk_arrival, early_late);
    reportLine(descriptionField(clk_vertex), clk_arrival,
               early_late, clk_end_rf);
  }
  reportLine(arrival_msg, search_->clkPathArrival(clk_path), early_late);
  reportBlankLine();
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportLimitShortHeader(const ReportField *field) const
{
  std::string line;
  reportDescription("Pin", line);
  line += ' ';
  reportField("Limit", field, line);
  line += ' ';
  reportField(field->title(), field, line);
  line += ' ';
  reportField("Slack", field, line);
  report_->reportLine(line);

  reportDashLine(field_description_->width() + field->width() * 3 + 3);
}

void
ReportPath::reportLimitShort(const ReportField *field,
                             const Pin *pin,
                             float value,
                             float limit,
                             float slack) const
{
  std::string line;
  const std::string pin_name = cmd_network_->pathName(pin);
  reportDescription(pin_name, line);
  line += ' ';
  reportField(limit, field, line);
  line += ' ';
  reportField(value, field, line);
  line += ' ';
  reportField(slack, field, line);
  line += (slack >= 0.0)
    ? " (MET)"
    : " (VIOLATED)";
  report_->reportLine(line);
}

void
ReportPath::reportLimitVerbose(const ReportField *field,
                               const Pin *pin,
                               const RiseFall *rf,
                               float value,
                               float limit,
                               float slack,
                               const Scene *scene,
                               const MinMax *min_max) const
{
  std::string line;
  line += "Pin ";
  line += cmd_network_->pathName(pin);
  line += ' ';
  if (rf)
    line += rf->shortName();
  else
    line += ' ';
  // Don't report scene if the default scene is the only scene.
  if (scene && multiScene()) {
    line += " (corner ";
    line += scene->name();
    line += ")";
  }
  report_->reportLine(line);

  line = min_max->to_string();
  line += ' ';
  line += field->name();
  line += ' ';
  reportField(limit, field, line);
  report_->reportLine(line);

  line = field->name();
  line += "     ";
  reportField(value, field, line);
  report_->reportLine(line);

  size_t name_width = field->name().size() + 5;
  reportDashLine(name_width + field->width());

  line = "Slack";
  for (size_t i = strlen("Slack"); i < name_width; i++)
    line += ' ';
  reportField(slack, field, line);
  line += (slack >= 0.0)
    ? " (MET)"
    : " (VIOLATED)";
  report_->reportLine(line);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportStartpoint(const PathEnd *end,
                             const PathExpanded &expanded) const
{
  const Path *path = end->path();
  const Sdc *sdc = path->sdc(this);
  const Path *start = expanded.startPath();
  const TimingArc *prev_arc = expanded.startPrevArc();
  const Edge *prev_edge = start->prevEdge(this);
  const Pin *pin = start->pin(graph_);
  const ClockEdge *clk_edge = path->clkEdge(this);
  const Clock *clk = path->clock(search_);
  std::string pin_name = cmd_network_->pathName(pin);
  if (pathFromClkPin(path, pin)) {
    const std::string &clk_name = clk->name();
    std::string reason = sta::format("clock source '{}'", clk_name);
    reportStartpoint(pin_name, reason);
  }
  else if (network_->isTopLevelPort(pin)) {
    if (clk
        && clk != sdc->defaultArrivalClock()) {
      const std::string &clk_name = clk->name();
      // Pin direction is "input" even for bidirects.
      std::string reason = sta::format("input port clocked by {}", clk_name);
      reportStartpoint(pin_name, reason);
    }
    else
      reportStartpoint(pin_name, "input port");
  }
  else if (network_->isLeaf(pin) && prev_arc) {
    Instance *inst = network_->instance(pin);
    std::string inst_name = cmd_network_->pathName(inst);
    if (clk_edge) {
      const RiseFall *clk_rf = clk_edge->transition();
      const Path *clk_path = expanded.clkPath();
      bool clk_inverted = clk_path
        && clk_rf != clk_path->transition(this);
      std::string clk_name = clkName(clk, clk_inverted);
      std::string_view reg_desc = edgeRegLatchDesc(prev_edge, prev_arc);
      std::string reason = sta::format("{} clocked by {}", reg_desc, clk_name);
      reportStartpoint(inst_name, reason);
    }
    else {
      std::string_view reg_desc = edgeRegLatchDesc(prev_edge, prev_arc);
      reportStartpoint(inst_name, std::string(reg_desc));
    }
  }
  else if (network_->isLeaf(pin)) {
    if (clk_edge) {
      Clock *clk = clk_edge->clock();
      if (clk != sdc->defaultArrivalClock()) {
        std::string reason = sta::format("internal path startpoint clocked by {}",
                                         clk->name());
        reportStartpoint(pin_name, reason);
      }
      else
        reportStartpoint(pin_name, "internal path startpoint");
    }
    else
      reportStartpoint(pin_name, "internal pin");
  }
  else
    reportStartpoint(pin_name, "");
}

bool
ReportPath::pathFromClkPin(const PathExpanded &expanded) const
{
  const Path *start = expanded.startPath();
  const Path *end = expanded.endPath();
  const Pin *start_pin = start->pin(graph_);
  return pathFromClkPin(end, start_pin);
}

bool
ReportPath::pathFromClkPin(const Path *path,
                           const Pin *start_pin) const
{
  const Clock *clk = path->clock(search_);
  return clk
    && clk->leafPins().contains(const_cast<Pin*>(start_pin));
}

void
ReportPath::reportStartpoint(std::string_view start,
                             const std::string &reason) const
{
  reportStartEndPoint(start, reason, "Startpoint");
}

void
ReportPath::reportUnclockedEndpoint(const PathEnd *end,
                                    std::string_view default_reason) const
{
  Vertex *vertex = end->vertex(this);
  Pin *pin = vertex->pin();
  if (network_->isTopLevelPort(pin)) {
    // Pin direction is "output" even for bidirects.
    reportEndpoint(cmd_network_->pathName(pin), "output port");
  }
  else if (network_->isLeaf(pin)) {
    VertexInEdgeIterator edge_iter(vertex, graph_);
    while (edge_iter.hasNext()) {
      Edge *edge = edge_iter.next();
      if (edge->role()->genericRole() == TimingRole::setup()) {
        Vertex *clk_vertex = edge->from(graph_);
        VertexOutEdgeIterator clk_edge_iter(clk_vertex, graph_);
        while (clk_edge_iter.hasNext()) {
          Edge *clk_edge = clk_edge_iter.next();
          Instance *inst = network_->instance(pin);
          std::string inst_name = cmd_network_->pathName(inst);
          if (clk_edge->role() == TimingRole::regClkToQ()) {
            std::string_view reason =
              regDesc(clk_edge->timingArcSet()->isRisingFallingEdge());
            reportEndpoint(inst_name, std::string(reason));
            return;
          }
          if (clk_edge->role() == TimingRole::latchEnToQ()) {
            std::string_view reason =
              latchDesc(clk_edge->timingArcSet()->isRisingFallingEdge());
            reportEndpoint(inst_name, std::string(reason));
            return;
          }
        }
      }
    }
    reportEndpoint(cmd_network_->pathName(pin), std::string(default_reason));
  }
  else
    reportEndpoint(cmd_network_->pathName(pin), "");
}

void
ReportPath::reportEndpoint(std::string_view end,
                           const std::string &reason) const
{
  reportStartEndPoint(end, reason, "Endpoint");
}

void
ReportPath::reportStartEndPoint(std::string_view pt,
                                const std::string &reason,
                                std::string_view key) const
{
  std::string line;
  // Account for punctuation in the line.
  size_t line_len = key.size() + 2 + pt.size() + 2 + reason.size() + 1;
  if (!no_split_
      && line_len > start_end_pt_width_) {
    line = std::string(key);
    line += ": ";
    line += pt;
    report_->reportLine(line);

    line.clear();
    for (size_t i = 0; i < key.size(); i++)
      line += ' ';

    line += "  (";
    line += reason;
    line += ")";
    report_->reportLine(line);
  }
  else {
    line = std::string(key);
    line += ": ";
    line += pt;
    line += " (";
    line += reason;
    line += ")";
    report_->reportLine(line);
  }
}

void
ReportPath::reportGroup(const PathEnd *end) const
{
  std::string line;
  line = "Path Group: ";
  PathGroup *group = end->pathGroup();
  line += group ? group->name() : "(none)";
  report_->reportLine(line);

  line = "Path Type: ";
  line += end->minMax(this)->to_string();
  report_->reportLine(line);

  if (modes_.size() > 1) {
    line = "Mode: ";
    line += end->path()->mode(this)->name();
    report_->reportLine(line);
  }

  if (multiScene()) {
    line = "Corner: ";
    line += end->path()->scene(this)->name();
    report_->reportLine(line);
  }
}

////////////////////////////////////////////////////////////////

std::string
ReportPath::checkRoleReason(const PathEnd *end) const
{
  return sta::format("{} time", end->checkRole(this)->to_string());
}

std::string
ReportPath::tgtClkName(const PathEnd *end) const
{
  const ClockEdge *tgt_clk_edge = end->targetClkEdge(this);
  const Clock *tgt_clk = tgt_clk_edge->clock();
  const RiseFall *clk_rf = tgt_clk_edge->transition();
  const RiseFall *clk_end_rf = end->targetClkEndTrans(this);
  return clkName(tgt_clk, clk_end_rf != clk_rf);
}

std::string
ReportPath::clkName(const Clock *clk,
                    bool inverted) const
{
  std::string name = clk->name();
  if (inverted)
    name += '\'';
  return name;
}

std::string_view
ReportPath::clkRegLatchDesc(const PathEnd *end) const
{
  // Goofy libraries can have registers with both rising and falling
  // clk->q timing arcs.  Try and match the timing check transition.
  const RiseFall *check_clk_rf=end->checkArc()->fromEdge()->asRiseFall();
  TimingArcSet *clk_set = nullptr;
  TimingArcSet *clk_rf_set = nullptr;
  Vertex *tgt_clk_vertex = end->targetClkPath()->vertex(this);
  VertexOutEdgeIterator iter(tgt_clk_vertex, graph_);
  while (iter.hasNext()) {
    Edge *edge = iter.next();
    TimingArcSet *arc_set = edge->timingArcSet();
    const TimingRole *role = arc_set->role();
    if (role == TimingRole::regClkToQ()
        || role == TimingRole::latchEnToQ()) {
      const RiseFall *arc_rf = arc_set->isRisingFallingEdge();
      clk_set = arc_set;
      if (arc_rf == check_clk_rf)
        clk_rf_set = arc_set;
    }
  }
  if (clk_rf_set)
    return checkRegLatchDesc(clk_rf_set->role(),
                             clk_rf_set->isRisingFallingEdge());
  else if (clk_set)
    return checkRegLatchDesc(clk_set->role(),
                             clk_set->isRisingFallingEdge());
  else
    return checkRegLatchDesc(TimingRole::regClkToQ(), check_clk_rf);
}

void
ReportPath::reportSrcPathArrival(const PathEnd *end,
                                 const PathExpanded &expanded) const
{
  reportBlankLine();
  reportSrcPath(end, expanded);
  reportLine("data arrival time", end->dataArrivalTimeOffset(this),
             end->pathEarlyLate(this));
  reportBlankLine();
}

void
ReportPath::reportSrcPath(const PathEnd *end,
                          const PathExpanded &expanded) const
{
  reportPathHeader();
  float src_clk_offset = end->sourceClkOffset(this);
  Arrival src_clk_insertion = end->sourceClkInsertionDelay(this);
  Arrival src_clk_latency = end->sourceClkLatency(this);
  const Path *path = end->path();
  reportSrcClkAndPath(path, expanded, src_clk_offset, src_clk_insertion,
                      src_clk_latency, end->isPathDelay());
}

void
ReportPath::reportSrcClkAndPath(const Path *path,
                                const PathExpanded &expanded,
                                float time_offset,
                                Arrival clk_insertion,
                                Arrival clk_latency,
                                bool is_path_delay) const
{
  const ClockEdge *clk_edge = path->clkEdge(this);
  const Mode *mode = path->mode(this);
  const Sdc *sdc = mode->sdc();
  const MinMax *min_max = path->minMax(this);
  if (clk_edge) {
    Clock *clk = clk_edge->clock();
    const RiseFall *clk_rf = clk_edge->transition();
    float clk_time = clk_edge->time() + time_offset;
    if (clk == sdc->defaultArrivalClock()) {
      if (!is_path_delay) {
        float clk_end_time = clk_time + time_offset;
        const EarlyLate *early_late = min_max;
        reportLine("clock (input port clock) (rise edge)",
                   clk_end_time, clk_end_time, early_late);
        reportLine(clkNetworkDelayIdealProp(false), 0.0, clk_end_time, early_late);
      }
      reportPath1(path, expanded, false, time_offset);
    }
    else {
      bool path_from_input = false;
      bool input_has_ref_path = false;
      Arrival clk_delay, clk_end_time;
      const Path *clk_path = expanded.clkPath();
      const RiseFall *clk_end_rf;
      if (clk_path) {
        clk_end_time = delaySum(search_->clkPathArrival(clk_path),
                                time_offset,
                                this);
        clk_delay = delayDiff(clk_end_time,
                              clk_time,
                              this);
        clk_end_rf = clk_path->transition(this);
      }
      else {
        // Path from input port or clk used as data.
        clk_end_rf = clk_rf;
        clk_delay = delaySum(clk_insertion, clk_latency, this);
        clk_end_time = delaySum(clk_time, clk_delay, this);

        const Path *first_path = expanded.startPath();
        const InputDelay *input_delay = pathInputDelay(first_path);
        if (input_delay) {
          path_from_input = true;
          const Pin *ref_pin = input_delay->refPin();
          if (ref_pin && clk->isPropagated()) {
            Path ref_path;
            pathInputDelayRefPath(first_path, input_delay, ref_path);
            if (!ref_path.isNull()) {
              const Arrival &ref_end_time = ref_path.arrival();
              clk_delay = delayDiff(ref_end_time,
                                    clk_time,
                                    this);
              clk_end_time = delaySum(ref_end_time,
                                      time_offset,
                                      this);
              input_has_ref_path = true;
            }
          }
        }
      }
      std::string clk_name = clkName(clk, clk_rf != clk_end_rf);

      bool clk_used_as_data = pathFromClkPin(expanded);
      bool is_prop = isPropagated(path);
      const EarlyLate *early_late = min_max;
      if (reportGenClkSrcPath(clk_path, clk, clk_rf, min_max, early_late, sdc)
          && !(path_from_input && !input_has_ref_path)) {
        reportClkLine(clk, clk_name, clk_end_rf, clk_time, min_max);
        reportGenClkSrcAndPath(path, clk, clk_rf, early_late, time_offset,
                               time_offset, clk_used_as_data, mode);
      }
      else if (clk_used_as_data 
               && pathFromGenPropClk(path, path->minMax(this))) {
        reportClkLine(clk, clk_name, clk_end_rf, clk_time, min_max);
        const ClkInfo *clk_info = path->tag(search_)->clkInfo();
        if (clk_info->isPropagated())
          reportClkSrcLatency(clk_insertion, clk_time, early_late);
        reportPath1(path, expanded, true, time_offset);
      }
      else if (is_prop
               && reportClkPath()
               && !(path_from_input && !input_has_ref_path)) {
        reportClkLine(clk, clk_name, clk_end_rf, clk_time, early_late);
        reportClkSrcLatency(clk_insertion, clk_time, early_late);
        reportPath1(path, expanded, false, time_offset);
      }
      else if (clk_used_as_data) {
        reportClkLine(clk, clk_name, clk_end_rf, clk_time, early_late);
        if (delayGreater(clk_insertion, 0.0, this))
          reportClkSrcLatency(clk_insertion, clk_time, early_late);
        if (reportClkPath())
          reportPath1(path, expanded, true, time_offset);
        else {
          Arrival clk_arrival = clk_end_time;
          Arrival end_arrival = delaySum(path->arrival(),
                                         time_offset,
                                         this);
          Delay clk_delay = delayDiff(end_arrival, clk_arrival, this);
          reportLine("clock network delay", clk_delay,
                     end_arrival, early_late);
          Vertex *end_vertex = path->vertex(this);
          reportLine(descriptionField(end_vertex),
                     end_arrival, early_late, clk_end_rf);
        }
      }
      else {
        if (is_path_delay) {
          if (delayGreater(clk_delay, 0.0, this))
            reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay,
                       clk_end_time, early_late);
        }
        else {
          reportClkLine(clk, clk_name, clk_end_rf, clk_time, min_max);
          Arrival clk_arrival = clk_end_time;
          reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay,
                     clk_arrival, early_late);
        }
        reportPath1(path, expanded, false, time_offset);
      }
    }
  }
  else
    reportPath1(path, expanded, false, time_offset);
}

void
ReportPath::reportTgtClk(const PathEnd *end) const
{
  reportTgtClk(end, 0.0);
}

void
ReportPath::reportTgtClk(const PathEnd *end,
                         float prev_time) const
{
  const Clock *clk = end->targetClk(this);
  const Path *clk_path = end->targetClkPath();
  reportTgtClk(end, prev_time, isPropagated(clk_path, clk));
}

void
ReportPath::reportTgtClk(const PathEnd *end,
                         float prev_time,
                         bool is_prop) const
{
  float src_offset = end->sourceClkOffset(this);
  reportTgtClk(end, prev_time, src_offset, is_prop);
}

void
ReportPath::reportTgtClk(const PathEnd *end,
                         float prev_time,
                         float src_offset,
                         bool is_prop) const
{
  const ClockEdge *clk_edge = end->targetClkEdge(this);
  const Clock *clk = clk_edge->clock();
  const RiseFall *clk_rf = clk_edge->transition();
  const RiseFall *clk_end_rf = end->targetClkEndTrans(this);
  std::string clk_name = clkName(clk, clk_end_rf != clk_rf);
  float clk_time = prev_time
    + end->targetClkTime(this)
    + end->targetClkMcpAdjustment(this)
    + src_offset;
  Arrival clk_delay = end->targetClkDelay(this);
  Arrival clk_arrival = delaySum(clk_delay, clk_time, this);
  const MinMax *min_max = end->path()->tgtClkMinMax(this);
  const Path *clk_path = end->targetClkPath();
  reportClkLine(clk, clk_name, clk_end_rf, prev_time, clk_time, min_max);
  const TimingRole *check_role = end->checkRole(this);
  if (is_prop && reportClkPath()) {
    float time_offset = prev_time
      + end->targetClkOffset(this)
      + end->targetClkMcpAdjustment(this);
    const EarlyLate *early_late = check_role->tgtClkEarlyLate();
    const Mode *mode = end->path()->mode(this);
    const Sdc *sdc = mode->sdc();
    if (reportGenClkSrcPath(clk_path, clk, clk_rf, min_max, early_late, sdc)) {
      float insertion_offset = 
        clk_path ? tgtClkInsertionOffet(clk_path, early_late) : 0.0;
      reportGenClkSrcAndPath(clk_path, clk, clk_rf, early_late, time_offset,
                             time_offset + insertion_offset, false, mode);
    }
    else {
      Arrival insertion = end->targetClkInsertionDelay(this);
      if (clk_path) {
        reportClkSrcLatency(insertion, clk_time, early_late);
        PathExpanded clk_expanded(clk_path, this);
        float insertion_offset = tgtClkInsertionOffet(clk_path, early_late);
        reportPath6(clk_path, clk_expanded, 0, is_prop, reportClkPath(),
                    delay_zero, time_offset + insertion_offset);
      }
      else {
        // Output departure.
        Arrival clk_arrival = delaySum(clk_time, clk_delay, this);
        reportLine(clkNetworkDelayIdealProp(clk->isPropagated()),
                   clk_delay, clk_arrival, min_max);
      }
    }
    reportClkUncertainty(end, clk_arrival);
    reportCommonClkPessimism(end, clk_arrival);
  }
  else {
    reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay,
               clk_arrival, min_max);
    reportClkUncertainty(end, clk_arrival);
    reportCommonClkPessimism(end, clk_arrival);
    if (clk_path) {
      Vertex *clk_vertex = clk_path->vertex(this);
      reportLine(descriptionField(clk_vertex),
                 delaySum(delaySum(prev_time,
                                   end->targetClkArrival(this),
                                   this),
                          end->sourceClkOffset(this),
                          this),
                 min_max, clk_end_rf);
    }
  }
}

float
ReportPath::tgtClkInsertionOffet(const Path *clk_path,
                                 const EarlyLate *early_late) const
{
  const ClkInfo *clk_info = clk_path->clkInfo(this);
  const Pin *src_pin = clk_info->clkSrc();
  const ClockEdge *clk_edge = clk_info->clkEdge();
  const Clock *clk = clk_edge->clock();
  const RiseFall *clk_rf = clk_edge->transition();
  const MinMax *min_max = clk_path->minMax(this);
  const Mode *mode = clk_path->mode(this);
  Arrival path_insertion = search_->clockInsertion(clk, src_pin, clk_rf,
                                                   min_max, min_max, mode);
  Arrival tgt_insertion = search_->clockInsertion(clk, src_pin, clk_rf,
                                                  min_max, early_late, mode);
  return delayAsFloat(delayDiff(tgt_insertion, path_insertion, this));
}

bool
ReportPath::pathFromGenPropClk(const Path *clk_path,
                               const EarlyLate *early_late) const
{
  const ClkInfo *clk_info = clk_path->tag(search_)->clkInfo();
  const ClockEdge *clk_edge = clk_info->clkEdge();
  if (clk_edge) {
    const Sdc *sdc = clk_path->sdc(this);
    const Clock *clk = clk_edge->clock();
    float insertion;
    bool exists;
    sdc->clockInsertion(clk, clk_info->clkSrc(),
                        clk_edge->transition(),
                        clk_path->minMax(this),
                        early_late,
                        insertion, exists);
    return !exists
      && clk->isGeneratedWithPropagatedMaster();
  }
  else
    return false;
}

bool
ReportPath::isGenPropClk(const Clock *clk,
                         const RiseFall *clk_rf,
                         const MinMax *min_max,
                         const EarlyLate *early_late,
                         const Sdc *sdc) const
{
  float insertion;
  bool exists;
  sdc->clockInsertion(clk, clk->srcPin(), clk_rf,
                      min_max, early_late,
                      insertion, exists);
  return !exists
    && clk->isGeneratedWithPropagatedMaster();
}

void
ReportPath::reportClkLine(const Clock *clk,
                          std::string_view clk_name,
                          const RiseFall *clk_rf,
                          Arrival clk_time,
                          const MinMax *min_max) const
{
  reportClkLine(clk, clk_name, clk_rf, 0.0, clk_time, min_max);
}

void
ReportPath::reportClkLine(const Clock *clk,
                          std::string_view clk_name,
                          const RiseFall *clk_rf,
                          Arrival prev_time,
                          Arrival clk_time,
                          const MinMax *min_max) const
{
  std::string_view rise_fall = asRiseFall(clk_rf);
  std::string clk_msg = sta::format("clock {} ({} edge)", clk_name, rise_fall);
  if (clk->isPropagated())
    reportLine(clk_msg,
               delayDiff(clk_time, prev_time, this),
               clk_time,
               min_max);
  else {
    // Report ideal clock slew.
    float clk_slew = clk->slew(clk_rf, min_max);
    reportLine(clk_msg,
               clk_slew,
               delayDiff(clk_time, prev_time, this),
               clk_time,
               min_max);
  }
}

bool
ReportPath::reportGenClkSrcPath(const Path *clk_path,
                                const Clock *clk,
                                const RiseFall *clk_rf,
                                const MinMax *min_max,
                                const EarlyLate *early_late,
                                const Sdc *sdc) const
{
  bool from_gen_prop_clk = clk_path
    ? pathFromGenPropClk(clk_path, early_late)
    : isGenPropClk(clk, clk_rf, min_max, early_late, sdc);
  return from_gen_prop_clk
    && format_ == ReportPathFormat::full_clock_expanded;
}

void
ReportPath::reportGenClkSrcAndPath(const Path *path,
                                   const Clock *clk,
                                   const RiseFall *clk_rf,
                                   const EarlyLate *early_late,
                                   float time_offset,
                                   float path_time_offset,
                                   bool clk_used_as_data,
                                   const Mode *mode) const
{
  const Pin *clk_pin = path
    ? path->clkInfo(search_)->clkSrc()
    : clk->defaultPin();
  float gclk_time = clk->edge(clk_rf)->time() + time_offset;
  bool skip_first_path = reportGenClkSrcPath1(clk, clk_pin, clk_rf,
                                              early_late, gclk_time,
                                              time_offset, clk_used_as_data,
                                              mode);
  if (path) {
    PathExpanded expanded(path, this);
    reportPath2(path, expanded, skip_first_path, clk_used_as_data,
                path_time_offset);
  }
}

bool
ReportPath::reportGenClkSrcPath1(const Clock *clk,
                                 const Pin *clk_pin,
                                 const RiseFall *clk_rf,
                                 const EarlyLate *early_late,
                                 float gclk_time,
                                 float time_offset,
                                 bool clk_used_as_data,
                                 const Mode *mode) const
{
  const Path *src_path = mode->genclks()->srcPath(clk, clk_pin, clk_rf, early_late);
  if (src_path) {
    const ClkInfo *src_clk_info = src_path->clkInfo(this);
    const ClockEdge *src_clk_edge = src_clk_info->clkEdge();
    const Clock *src_clk = src_clk_info->clock();
    const MinMax *min_max = src_path->minMax(this);
    if (src_clk) {
      bool skip_first_path = false;
      const RiseFall *src_clk_rf = src_clk_edge->transition();
      const Pin *src_clk_pin = src_clk_info->clkSrc();
      if (src_clk->isGeneratedWithPropagatedMaster()
          && src_clk_info->isPropagated()) {
        skip_first_path = reportGenClkSrcPath1(src_clk, src_clk_pin,
                                               src_clk_rf, early_late,
                                               gclk_time, time_offset,
                                               clk_used_as_data,
                                               mode);
      }
      else {
        const Mode *mode = src_path->mode(this);
        const Arrival insertion = search_->clockInsertion(src_clk, src_clk_pin,
                                                          src_clk_rf,
                                                          min_max,
                                                          early_late, mode);
        reportClkSrcLatency(insertion, gclk_time, early_late);
      }
      PathExpanded src_expanded(src_path, this);
      reportPath2(src_path, src_expanded, skip_first_path,
                  clk_used_as_data, gclk_time);
      if (!clk->isPropagated())
        reportLine("clock network delay (ideal)", 0.0,
                   src_path->arrival(), min_max);
    }
  }
  else {
    if (clk->isPropagated())
      reportClkSrcLatency(0.0, gclk_time, early_late);
    else if (!clk_used_as_data)
      reportLine("clock network delay (ideal)", 0.0, gclk_time, MinMax::max());
  }
  return src_path != nullptr;
}

void
ReportPath::reportClkSrcLatency(Arrival insertion,
                                float clk_time,
                                const EarlyLate *early_late) const
{
  Arrival clk_arrival = delaySum(clk_time,
                                 insertion,
                                 this);
  reportLine("clock source latency", insertion, clk_arrival, early_late);
}

void
ReportPath::reportPathLine(const Path *path,
                           const Delay &incr,
                           const Arrival &time,
                           std::string_view line_case) const
{
  Vertex *vertex = path->vertex(this);
  Pin *pin = vertex->pin();
  const std::string what = descriptionField(vertex);
  const RiseFall *rf = path->transition(this);
  bool is_driver = network_->isDriver(pin);
  const EarlyLate *early_late = path->minMax(this);
  const Scene *scene = path->scene(this);
  const MinMax *min_max = path->minMax(this);
  DcalcAPIndex slew_index = path->dcalcAnalysisPtIndex(this);
  Slew slew = graph_->slew(vertex, rf, slew_index);
  float cap = field_blank_;
  Instance *inst = network_->instance(pin);
  std::string src_attr;
  if (inst)
    src_attr = network_->getAttribute(inst, "src");
  // Don't show capacitance field for input pins.
  if (is_driver && field_capacitance_->enabled())
    cap = graph_delay_calc_->loadCap(pin, rf, scene, min_max);
  reportLine(what, path, cap, slew, field_blank_, incr, field_blank_,
             time, false, early_late, rf, src_attr, line_case);
}

void
ReportPath::reportRequired(const PathEnd *end,
                           const std::string& margin_msg) const
{
  Required req_time = end->requiredTimeOffset(this);
  const EarlyLate *early_late = end->clkEarlyLate(this);
  float macro_clk_tree_delay = end->macroClkTreeDelay(this);
  ArcDelay margin = end->margin(this);
  if (end->minMax(this) == MinMax::min()) {
    margin = delayDiff(delay_zero, margin, this);
    macro_clk_tree_delay = -macro_clk_tree_delay;
  }
  if (macro_clk_tree_delay != 0.0)
    reportLine("macro clock tree delay", -macro_clk_tree_delay,
               delaySum(req_time, margin, this), early_late);
  reportLine(margin_msg,
             delayDiff(delay_zero, margin, this),
             req_time,
             early_late);
  reportLine("data required time", req_time, early_late);
  reportDashLine();
}

void
ReportPath::reportSlack(const PathEnd *end) const
{
  const EarlyLate *early_late = end->pathEarlyLate(this);
  reportLine("data required time", end->requiredTimeOffset(this),
             early_late->opposite());
  reportLineNegative("data arrival time", end->dataArrivalTimeOffset(this), early_late);
  reportDashLine();
  reportSlack(end->slack(this));
}

void
ReportPath::reportSlack(Slack slack) const
{
  const EarlyLate *early_late = EarlyLate::early();
  std::string_view msg = (delayAsFloat(slack, early_late, this) >= 0.0)
    ? "slack (MET)"
    : "slack (VIOLATED)";
  reportLine(msg, slack, early_late);
}

void
ReportPath::reportSpaceSlack(const PathEnd *end,
                             std::string &result) const
{
  Slack slack = end->slack(this);
  reportSpaceSlack(slack, result);
}

void
ReportPath::reportSpaceSlack(Slack slack,
                             std::string &result) const
{
  const EarlyLate *early_late = EarlyLate::early();
  reportSpaceFieldDelay(slack, early_late, result);
  result += (delayAsFloat(slack, early_late, this) >= 0.0)
    ? " (MET)"
    : " (VIOLATED)";
}

void
ReportPath::reportCommonClkPessimism(const PathEnd *end,
                                     Arrival &clk_arrival) const
{
  if (variables_->crprEnabled()) {
    Crpr pessimism = end->checkCrpr(this);
    clk_arrival = delaySum(clk_arrival, pessimism, this);
    reportLine("clock reconvergence pessimism", pessimism, clk_arrival,
               end->clkEarlyLate(this));
  }
}

void
ReportPath::reportClkUncertainty(const PathEnd *end,
                                 Arrival &clk_arrival) const
{
  const EarlyLate *early_late = end->clkEarlyLate(this);
  float uncertainty = end->targetNonInterClkUncertainty(this);
  clk_arrival = delaySum(clk_arrival,
                         uncertainty,
                         this);
  if (uncertainty != 0.0)
    reportLine("clock uncertainty", uncertainty, clk_arrival, early_late);
  float inter_uncertainty = end->interClkUncertainty(this);
  clk_arrival = delaySum(clk_arrival,
                         inter_uncertainty,
                         this);
  if (inter_uncertainty != 0.0)
    reportLine("inter-clock uncertainty", inter_uncertainty,
               clk_arrival, early_late);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportPath(const PathEnd *end,
                       const PathExpanded &expanded) const
{
  reportPathHeader();
  // Source clk offset for path delays removes clock phase time.
  float src_clk_offset = end->sourceClkOffset(this);
  reportPath1(end->path(), expanded, pathFromClkPin(expanded), src_clk_offset);
}

void
ReportPath::reportPath(const Path *path) const
{
  switch (format_) {
  case ReportPathFormat::full:
  case ReportPathFormat::full_clock:
  case ReportPathFormat::full_clock_expanded:
    reportPathFull(path);
    break;
  case ReportPathFormat::json:
    reportJson(path);
    break;
  case ReportPathFormat::shorter:
  case ReportPathFormat::endpoint:
  case ReportPathFormat::summary:
  case ReportPathFormat::slack_only:
    report_->report("Format not supported.");
    break;
  }
}

void
ReportPath::reportPathFull(const Path *path) const
{
  reportPathHeader();
  PathExpanded expanded(path, this);
  reportSrcClkAndPath(path, expanded, 0.0, delay_zero, delay_zero, false);
}

////////////////////////////////////////////////////////////////

// Main entry point for reporting a path.
void
ReportPath::reportPath1(const Path *path,
                        const PathExpanded &expanded,
                        bool clk_used_as_data,
                        float time_offset) const
{
  reportPath2(path, expanded, false, clk_used_as_data, time_offset);
}

// Alternate entry point with skip_first_path arg.
void
ReportPath::reportPath2(const Path *path,
                        const PathExpanded &expanded,
                        bool skip_first_path,
                        bool clk_used_as_data,
                        float time_offset) const
{
  bool clk_is_propagated  = path->clkInfo(search_)->isPropagated();
  bool report_clk_path = (reportClkPath() && clk_is_propagated)
    || clk_used_as_data;
  bool propagated_clk = clk_is_propagated || clk_used_as_data;
  reportPath4(path, expanded, skip_first_path, propagated_clk,
              report_clk_path, time_offset);
}

// Alternate entry point with report_clk_path arg.
void
ReportPath::reportPath3(const Path *path,
                        const PathExpanded &expanded,
                        bool report_clk_path,
                        float time_offset) const
{
  bool propagated_clk  = path->clkInfo(search_)->isPropagated();
  reportPath4(path, expanded, false, propagated_clk,
              report_clk_path, time_offset);
}

void
ReportPath::reportPath4(const Path *path,
                        const PathExpanded &expanded,
                        bool skip_first_path,
                        bool propagated_clk,
                        bool report_clk_path,
                        float time_offset) const
{
  const Path *d_path, *q_path;
  Edge *d_q_edge;
  expanded.latchPaths(d_path, q_path, d_q_edge);
  if (d_path) {
    Arrival latch_time_given, latch_enable_time;
    Path *latch_enable_path;
    latches_->latchTimeGivenToStartpoint(d_path, q_path, d_q_edge,
                                         latch_time_given, latch_enable_path);
    if (latch_enable_path) {
      const EarlyLate *early_late = latch_enable_path->minMax(this);
      latch_enable_time = search_->clkPathArrival(latch_enable_path);
      if (report_clk_path) {
        PathExpanded enable_expanded(latch_enable_path, this);
        // Report the path to the latch enable.
        reportPath5(latch_enable_path, enable_expanded, skip_first_path,
                    propagated_clk, report_clk_path, time_offset);
      }
      Arrival time = delaySum(latch_enable_time,
                              latch_time_given,
                              this);
      Arrival incr = latch_time_given;
      if (delayGreaterEqual(incr, 0.0, this))
        reportLine("time given to startpoint", incr, time, early_late);
      else
        reportLine("time borrowed from startpoint", incr, time, early_late);
      // Override latch D arrival with enable + given.
      reportPathLine(expanded.path(0), delay_zero, time, "latch_D");
      reportPath6(path, expanded, 1, propagated_clk, report_clk_path,
                  delaySum(latch_enable_time,
                           latch_time_given,
                           this),
                  time_offset);
    }
  }
  else
    reportPath5(path, expanded, skip_first_path, propagated_clk,
                report_clk_path, time_offset);
}

void
ReportPath::reportPath5(const Path *path,
                        const PathExpanded &expanded,
                        bool skip_first_path,
                        bool propagated_clk,
                        bool report_clk_path,
                        float time_offset) const
{
  size_t path_first_index = 0;
  Arrival prev_time = 0.0;
  if (skip_first_path) {
    path_first_index = 1;
    const Path *start = expanded.path(0);
    prev_time = delaySum(start->arrival(),
                         time_offset,
                         this);
  }
  reportPath6(path, expanded, path_first_index, propagated_clk,
              report_clk_path, prev_time, time_offset);
}

// This does the real workk of reporting an expanded path.
void
ReportPath::reportPath6(const Path *path,
                        const PathExpanded &expanded,
                        size_t path_first_index,
                        bool propagated_clk,
                        bool report_clk_path,
                        Arrival prev_time,
                        float time_offset) const
{
  size_t path_last_index = expanded.size() - 1;
  const Scene *scene = path->scene(this);
  const MinMax *min_max = path->minMax(this);
  const Sdc *sdc = path->sdc(this);
  DcalcAPIndex slew_index = path->dcalcAnalysisPtIndex(this);
  const Path *clk_path = expanded.clkPath();
  Vertex *clk_start = clk_path ? clk_path->vertex(this) : nullptr;
  for (size_t i = path_first_index; i <= path_last_index; i++) {
    const Path *path1 = expanded.path(i);
    const TimingArc *prev_arc = path1->prevArc(this);
    Vertex *vertex = path1->vertex(this);
    Pin *pin = vertex->pin();
    Arrival time = delaySum(path1->arrival(), time_offset, this);
    Delay incr = 0.0;
    std::string_view line_case;
    bool is_clk_start = path1->vertex(this) == clk_start;
    bool is_clk = path1->isClock(search_);
    Instance *inst = network_->instance(pin);
    std::string src_attr;
    if (inst)
      src_attr = network_->getAttribute(inst, "src");
    // Always show the search start point (register clk pin).
    // Skip reporting the clk tree unless it is requested.
    if (is_clk_start
        || report_clk_path
        || !is_clk) {
      const RiseFall *rf = path1->transition(this);
      Slew slew = graph_->slew(vertex, rf, slew_index);
      if (prev_arc == nullptr) {
        // First path.
        reportInputExternalDelay(path1, time_offset);
        size_t next_index = i + 1;
        const Path *next_path = expanded.path(next_index);
        if (network_->isTopLevelPort(pin)
            && next_path
            && !nextArcAnnotated(next_path, next_index, expanded, slew_index)
            && hasExtInputDriver(pin, rf, min_max, sdc)) {
          // Pin is an input port with drive_cell/drive_resistance.
          // The delay calculator annotates wire delays on the edges
          // from the input to the loads.  Report the wire delay on the
          // input pin instead.
          Arrival next_time = delaySum(next_path->arrival(),
                                       time_offset,
                                       this);
          incr = delayIncr(next_time, time, min_max);
          time = next_time;
          line_case = "input_drive";
        }
        else if (is_clk) {
          if (!propagated_clk)
            // Clock latency at path endpoint in case latency was set
            // on a clock pin other than the clock source.
            time = delaySum(search_->clkPathArrival(path1), time_offset, this);
          incr = 0.0;
          line_case = "clk_first";
        }
        else {
          incr = 0.0;
          line_case = "first";
        }
      }
      else if (is_clk_start
               && is_clk
               && !report_clk_path) {
        // Clock start point and clock path are not reported.
        incr = 0.0;
        if (!propagated_clk) {
          // Ideal clock.
           const ClockEdge *src_clk_edge = path->clkEdge(this);
          time = delaySum(search_->clkPathArrival(path1),
                          time_offset,
                          this);
          if (src_clk_edge) {
            Clock *src_clk = src_clk_edge->clock();
            const RiseFall *src_clk_rf = src_clk_edge->transition();
            slew = src_clk->slew(src_clk_rf, min_max);
          }
        }
        line_case = "clk_start";
      }
      else if (is_clk
               && report_clk_path
               && !propagated_clk) {
        // Zero the clock network delays for ideal clocks.
        incr = 0.0;
        time = prev_time;
        const ClockEdge *src_clk_edge = path->clkEdge(this);
        const Clock *src_clk = src_clk_edge->clock();
        const RiseFall *src_clk_rf = src_clk_edge->transition();
        slew = src_clk->slew(src_clk_rf, min_max);
        line_case = "clk_ideal";
      }
      else if (is_clk && !is_clk_start) {
        incr = delayIncr(time, prev_time, min_max);
        line_case = "clk_prop";
      }
      else {
        incr = delayIncr(time, prev_time, min_max);
        line_case = "normal";
      }

      if (vertex->isDriver(network_)) {
        // Report delay arc pocv variation between input and driver.
        reportVariation(path1);
        float cap = field_blank_;
        float fanout = field_blank_;
        if (field_capacitance_->enabled())
          cap = graph_delay_calc_->loadCap(pin, rf, scene, min_max);
        if (field_fanout_->enabled())
          fanout = drvrFanout(vertex, scene, min_max);
        const std::string what = descriptionField(vertex);
        reportLine(what, path1, cap, slew, fanout,
                   incr, field_blank_, time, false, min_max, rf, src_attr,
                   line_case);

        if (report_net_) {
          const std::string what2 = descriptionNet(pin);
          reportLine(what2, path1, field_blank_, field_blank_, field_blank_,
                     field_blank_, field_blank_, field_blank_, false, min_max,
                     nullptr, src_attr, "");
        }
        prev_time = time;
      }
      else {
        reportHierPinsThru(path1);
        if (report_input_pin_
            || (i == 0)
            || (i == path_last_index)
            || is_clk_start) {
          const std::string what = descriptionField(vertex);
          reportLine(what, path1, field_blank_, slew, field_blank_,
                     incr, field_blank_, time, false, min_max, rf, src_attr,
                     line_case);
          prev_time = time;
        }
      }
    }
    else
      prev_time = time;
  }
}

void
ReportPath::reportVariation(const Path *path) const
{
  if (field_variation_->enabled()) {
    const Edge *prev_edge = path->prevEdge(this);
    if (prev_edge) {
      const TimingArc *prev_arc = path->prevArc(this);
      const MinMax *min_max = path->minMax(this);
      DcalcAPIndex slew_index = path->dcalcAnalysisPtIndex(this);
      const ArcDelay &arc_delay=graph_->arcDelay(prev_edge, prev_arc,slew_index);
      switch (variables_->pocvMode()) {
      case PocvMode::normal: {
        float std_dev = arc_delay.stdDev();
        reportLine("sigma", path, field_blank_, field_blank_, field_blank_,
                   field_blank_, std_dev, field_blank_, true, min_max,
                   nullptr, "", "");
        break;
      }
      case PocvMode::skew_normal: {
        float mean = arc_delay.mean();
        reportLine("mean", path, field_blank_, field_blank_, field_blank_,
                   field_blank_, mean, field_blank_, true, min_max,
                   nullptr, "", "");
        float mean_shift = arc_delay.meanShift();
        reportLine("mean_shift", path, field_blank_, field_blank_, field_blank_,
                   field_blank_, mean_shift, field_blank_, true, min_max,
                   nullptr, "", "");
        float std_dev = arc_delay.stdDev();
        reportLine("std_dev", path, field_blank_, field_blank_, field_blank_,
                   field_blank_, std_dev, field_blank_, true, min_max,
                   nullptr, "", "");
        // skewness is dimensionless, so scale it to the field's time units.
        float skewness = arc_delay.skewness() * units_->timeUnit()->scale();
        reportLine("skewness", path, field_blank_, field_blank_, field_blank_,
                   field_blank_, skewness, field_blank_, true, min_max,
                   nullptr, "", "");
        break;
      }
      default:
        break;
      }
    }
  }
}

Delay
ReportPath::delayIncr(const Delay &time,
                      const Delay &prev,
                      const MinMax *min_max) const
{
  return delayAsFloat(time, min_max, this) - delayAsFloat(prev, min_max, this);
}

void
ReportPath::reportHierPinsThru(const Path *path) const
{
  if (report_hier_pins_) {
    const Edge *prev_edge = path->prevEdge(this);
    if (prev_edge && prev_edge->isWire()) {
      for (const Pin *hpin : hierPinsThruEdge(prev_edge, network_, graph_)) {
        const std::string what = descriptionField(hpin);
        reportLine(what, path, field_blank_, field_blank_, field_blank_,
                   field_blank_, field_blank_, field_blank_, false,
                   path->minMax(this), nullptr, "", "");
      }
    }
  }
}

bool
ReportPath::nextArcAnnotated(const Path *next_path,
                             size_t next_index,
                             const PathExpanded &expanded,
                             DcalcAPIndex ap_index) const
{
  const TimingArc *arc = expanded.path(next_index)->prevArc(this);
  Edge *edge = next_path->prevEdge(this);
  return graph_->arcDelayAnnotated(edge, arc, ap_index);
}

std::string
ReportPath::descriptionField(const Vertex *vertex) const
{
  return descriptionField(vertex->pin());
}

std::string
ReportPath::descriptionField(const Pin *pin) const
{
  std::string name2;
  if (network_->isTopLevelPort(pin)) {
    PortDirection *dir = network_->direction(pin);
    // Translate port direction.  Note that this is intentionally
    // inconsistent with the direction reported for top level ports as
    // startpoints.
    if (dir->isInput())
      name2 = "in";
    else if (dir->isOutput() || dir->isTristate())
      name2 = "out";
    else if (dir->isBidirect())
      name2 = "inout";
    else
      name2 = "?";
  }
  else {
    Instance *inst = network_->instance(pin);
    name2 = network_->cellName(inst);
  }
  std::string pin_name = cmd_network_->pathName(pin);
  return sta::format("{} ({})", pin_name, name2);
}

std::string
ReportPath::descriptionNet(const Pin *pin) const
{
  if (network_->isTopLevelPort(pin))
    return sta::format("{} (net)", cmd_network_->pathName(pin));
  else {
    Net *net = network_->net(pin);
    if (net) {
      Net *highest_net = network_->highestNetAbove(net);
      std::string net_name = cmd_network_->pathName(highest_net);
      return sta::format("{} (net)", net_name);
    }
    else
      return "(unconnected)";
  }
}

float
ReportPath::drvrFanout(Vertex *drvr,
                       const Scene *scene,
                       const MinMax *min_max) const
{
  const Sdc *sdc = scene->sdc();
  float fanout = 0.0;
  VertexOutEdgeIterator iter(drvr, graph_);
  while (iter.hasNext()) {
    Edge *edge = iter.next();
    if (edge->isWire()) {
      Pin *pin = edge->to(graph_)->pin();
      if (network_->isTopLevelPort(pin)) {
        // Output port counts as a fanout.
        Port *port = network_->port(pin);
        fanout += sdc->portExtFanout(port, min_max) + 1;
      }
      else
        fanout++;
    }
  }
  return fanout;
}

bool
ReportPath::hasExtInputDriver(const Pin *pin,
                              const RiseFall *rf,
                              const MinMax *min_max,
                              const Sdc *sdc) const
{
  Port *port = network_->port(pin);
  InputDrive *drive = sdc->findInputDrive(port);
  return (drive
          && (drive->hasDriveResistance(rf, min_max)
              || drive->hasDriveCell(rf, min_max)));
}

void
ReportPath::reportInputExternalDelay(const Path *first_path,
                                     float time_offset) const
{
  const Pin *first_pin = first_path->pin(graph_);
  if (!pathFromClkPin(first_path, first_pin)) {
    const RiseFall *rf = first_path->transition(this);
    Arrival time = delaySum(first_path->arrival(),
                            time_offset,
                            this);
    const EarlyLate *early_late = first_path->minMax(this);
    InputDelay *input_delay = pathInputDelay(first_path);
    if (input_delay) {
      const Pin *ref_pin = input_delay->refPin();
      if (ref_pin) {
        Path ref_path;
        pathInputDelayRefPath(first_path, input_delay, ref_path);
        if (!ref_path.isNull() && reportClkPath()) {
          PathExpanded ref_expanded(&ref_path, this);
          reportPath3(&ref_path, ref_expanded, true, 0.0);
        }
      }
      float input_arrival =
        input_delay->delays()->value(rf, first_path->minMax(this));
      reportLine("input external delay", input_arrival, time,
                 early_late, rf);
    }
    else if (network_->isTopLevelPort(first_pin))
      reportLine("input external delay", 0.0, time, early_late, rf);
  }
}

// Return the input delay at the start of a path.
InputDelay *
ReportPath::pathInputDelay(const Path *first_path) const
{
  return first_path->tag(this)->inputDelay();
}

void
ReportPath::pathInputDelayRefPath(const Path *path,
                                  const InputDelay *input_delay,
                                  // Return value.
                                  Path &ref_path) const
{
  const Pin *ref_pin = input_delay->refPin();
  const RiseFall *ref_rf = input_delay->refTransition();
  Vertex *ref_vertex = graph_->pinDrvrVertex(ref_pin);
  if (ref_vertex) {
    const ClockEdge *clk_edge = path->clkEdge(this);
    VertexPathIterator path_iter(ref_vertex, path->scene(this),
                                 path->minMax(this), ref_rf,this);
    while (path_iter.hasNext()) {
      Path *path = path_iter.next();
      if (path->isClock(this)
          && path->clkEdge(this) == clk_edge) {
        ref_path = path;
        break;
      }
    }
  }
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportPathHeader() const
{
  std::string line;
  bool first_field = true;
  for (const ReportField *field : fields_) {
    if (field->enabled()) {
      if (!first_field)
        line += ' ';
      reportField(field->title(), field, line);
      first_field = false;
    }
  }
  trimRight(line);
  report_->reportLine(line);
  reportDashLine();
}

// Report total.
void
ReportPath::reportLine(std::string_view what,
                       Delay total,
                       const EarlyLate *early_late) const
{
  reportLine(what, nullptr, field_blank_, field_blank_, field_blank_, field_blank_,
             field_blank_, total, false, early_late, nullptr, "", "");
}

// Report negative total.
void
ReportPath::reportLineNegative(std::string_view what,
                               Delay total,
                               const EarlyLate *early_late) const
{
  reportLine(what, nullptr, field_blank_, field_blank_, field_blank_,
             field_blank_, field_blank_, total, true /* tota_with_minus */,
             early_late, nullptr, "", "");
}

// Report total, and transition suffix.
void
ReportPath::reportLine(std::string_view what,
                       Delay total,
                       const EarlyLate *early_late,
                       const RiseFall *rf) const
{
  reportLine(what, nullptr, field_blank_, field_blank_, field_blank_,
             field_blank_, field_blank_, total, false, early_late, rf, "", "");
}

// Report increment, and total.
void
ReportPath::reportLine(std::string_view what,
                       const Delay &incr,
                       const Delay &total,
                       const EarlyLate *early_late) const
{
  reportLine(what, nullptr, field_blank_, field_blank_, field_blank_,
             incr, field_blank_, total, false, early_late, nullptr, "", "");
}

// Report increment, total, and transition suffix.
void
ReportPath::reportLine(std::string_view what,
                       const Delay &incr,
                       const Delay &total,
                       const EarlyLate *early_late,
                       const RiseFall *rf) const
{
  reportLine(what, nullptr, field_blank_, field_blank_, field_blank_,
             incr, field_blank_, total, false, early_late, rf, "", "");
}

// Report slew, increment, and total.
void
ReportPath::reportLine(std::string_view what,
                       const Slew &slew,
                       const Delay &incr,
                       const Delay &total,
                       const EarlyLate *early_late) const
{
  reportLine(what, nullptr, field_blank_, slew, field_blank_,
             incr, field_blank_, total, false, early_late, nullptr, "", "");
}

void
ReportPath::reportLine(std::string_view what,
                       const Path *path,
                       float cap,
                       const Slew &slew,
                       float fanout,
                       const Delay &incr,
                       float variation,
                       const Delay &total,
                       bool total_with_minus,
                       const EarlyLate *early_late,
                       const RiseFall *rf,
                       std::string_view src_attr,
                       std::string_view line_case) const
{
  std::string line;
  size_t field_index = 0;
  bool first_field = true;
  for (const ReportField *field : fields_) {
    bool last_field = field_index == (fields_.size() - 1);
    
    if (field->enabled()) {
      if (!first_field)
        line += ' ';

      if (field == field_description_)
        reportDescription(what, first_field, last_field, line);
      else if (field == field_fanout_) {
        if (fanout == field_blank_)
          reportFieldBlank(field, line);
        else
          line += sta::format("{:{}}",
                              fanout,
                              field_fanout_->width());
      }
      else if (field == field_capacitance_)
        reportField(cap, field, line);
      else if (field == field_slew_)
        reportFieldDelay(slew, early_late, field, line);
      else if (field == field_incr_)
        reportFieldDelay(incr, early_late, field, line);
      else if (field == field_variation_)
        reportFieldDelay(variation, early_late, field, line);
      else if (field == field_total_) {
        if (total_with_minus)
          reportFieldDelayMinus(total, early_late, field, line);
        else
          reportFieldDelay(total, early_late, field, line);
      }
      else if (field == field_edge_) {
        if (rf)
          reportField(rf->shortName(), field, line);
        else
          reportFieldBlank(field, line);
      }
      else if (field == field_variation_)
        reportFieldBlank(field, line);
      else if (field == field_src_attr_) {
        if (!src_attr.empty())
          reportField(src_attr, field, line);
        else
          reportFieldBlank(field, line);
      }
      else if (field == field_case_)
        reportField(line_case, field, line);
      else if (field->getValue())
        reportField(field->value(path, this), field, line);

      first_field = false;
    }
    field_index++;
  }
  // Trim trailing spaces and report the line.
  std::string line_stdstr = line;
  trimRight(line_stdstr);
  report_->reportLine(line_stdstr);
}

////////////////////////////////////////////////////////////////

// Only the total field.
void
ReportPath::reportLineTotal(std::string_view what,
                            const Delay &incr,
                            const EarlyLate *early_late) const
{
  reportLineTotal1(what, incr, false, early_late);
}

// Only the total field and always with leading minus sign.
void
ReportPath::reportLineTotalMinus(std::string_view what,
                                 const Delay &decr,
                                 const EarlyLate *early_late) const
{
  reportLineTotal1(what, decr, true, early_late);
}

void
ReportPath::reportLineTotal1(std::string_view what,
                             const Delay &incr,
                             bool incr_with_minus,
                             const EarlyLate *early_late) const
{
  std::string line;
  reportDescription(what, line);
  line += ' ';
  if (incr_with_minus)
    reportFieldDelayMinus(incr, early_late, field_total_, line);
  else
    reportFieldDelay(incr, early_late, field_total_, line);
  report_->reportLine(line);
}

void
ReportPath::reportDashLineTotal() const
{
  reportDashLine(field_description_->width() + field_total_->width() + 1);
}

////////////////////////////////////////////////////////////////

void
ReportPath::reportDescription(std::string_view what,
                              std::string &line) const
{
  reportDescription(what, false, false, line);
}

void
ReportPath::reportDescription(std::string_view what,
                              bool first_field,
                              bool last_field,
                              std::string &line) const
{
  line += what;
  size_t length = what.size();
  if (!no_split_
      && first_field
      && length > field_description_->width()) {
    reportBlankLine();
    for (size_t i = 0; i < field_description_->width(); i++)
      line += ' ';
  }
  else if (!last_field) {
    for (size_t i = length; i < field_description_->width(); i++)
      line += ' ';
  }
}

void
ReportPath::reportFieldTime(float value,
                            ReportField *field,
                            std::string &line) const
{
  if (delayAsFloat(value) == field_blank_)
    reportFieldBlank(field, line);
  else {
    std::string str = units_->timeUnit()->asString(value, digits_);
    if (str == minus_zero_)
      // Filter "-0.00" fields.
      str = plus_zero_;
    reportField(str, field, line);
  }
}

void
ReportPath::reportSpaceFieldTime(float value,
                                 std::string &line) const
{
  line += ' ';
  reportFieldTime(value, field_total_, line);
}

void
ReportPath::reportSpaceFieldDelay(const Delay &value,
                                  const EarlyLate *early_late,
                                  std::string &line) const
{
  line += ' ';
  reportTotalDelay(value, early_late, line);
}

void
ReportPath::reportTotalDelay(const Delay &value,
                             const EarlyLate *early_late,
                             std::string &line) const
{
  std::string str = delayAsString(value, early_late, digits_, this);
  if (str == minus_zero_)
    // Filter "-0.00" fields.
    str = plus_zero_;
  reportField(str, field_total_, line);
}

// Total time always with leading minus sign.
void
ReportPath::reportFieldDelayMinus(const Delay &value,
                                  const EarlyLate *early_late,
                                  const ReportField *field,
                                  std::string &line) const
{
  if (delayAsFloat(value) == field_blank_)
    reportFieldBlank(field, line);
  else {
    // Opposite min/max for negative value.
    std::string str = delayAsString(delayDiff(delay_zero, value, this),
                                    early_late->opposite(), digits_, this);
    if (str == plus_zero_)
      // Force leading minus sign.
      str = minus_zero_;
    reportField(str, field, line);
  }
}

void
ReportPath::reportFieldDelay(const Delay &value,
                             const EarlyLate *early_late,
                             const ReportField *field,
                             std::string &line) const
{
  if (value.mean() == field_blank_)
    reportFieldBlank(field, line);
  else {
    std::string str = delayAsString(value, early_late, digits_, this);
    if (str == minus_zero_)
      // Filter "-0.00" fields.
      str = plus_zero_;
    reportField(str, field, line);
  }
}

void
ReportPath::reportField(float value,
                        const ReportField *field,
                        std::string &line) const
{
  if (value == field_blank_)
    reportFieldBlank(field, line);
  else {
    Unit *unit = field->unit();
    if (unit) {
      std::string value_str = unit->asString(value, digits_);
      reportField(value_str, field, line);
    }
    else {
      // fanout
      std::string value_str = sta::format("{:.0f}", value);
      reportField(value_str, field, line);
    }
  }
}

void
ReportPath::reportField(std::string_view value,
                        const ReportField *field,
                        std::string &line) const
{
  if (field->leftJustify())
    line += value;
  for (size_t i = static_cast<int>(value.size()); i < field->width(); i++)
    line += ' ';
  if (!field->leftJustify())
    line += value;
}

void
ReportPath::reportFieldBlank(const ReportField *field,
                             std::string &line) const
{
  line += field->blank();
}

void
ReportPath::reportDashLine() const
{
  std::string line;
  for (const ReportField *field : fields_) {
    if (field->enabled()) {
      for (size_t i = 0; i < field->width(); i++)
        line += '-';
    }
  }
  line += "------";
  report_->reportLine(line);
}

void
ReportPath::reportDashLine(int line_width) const
{
  std::string line;
  for (int i = 0; i < line_width; i++)
    line += '-';
  report_->reportLine(line);
}

void
ReportPath::reportBlankLine() const
{
  report_->reportBlankLine();
}

bool
ReportPath::reportClkPath() const
{
  return format_ == ReportPathFormat::full_clock
    || format_ == ReportPathFormat::full_clock_expanded;
}

////////////////////////////////////////////////////////////////

std::string_view
ReportPath::asRisingFalling(const RiseFall *rf) const
{
  if (rf == RiseFall::rise())
    return "rising";
  else
    return "falling";
}

std::string_view
ReportPath::asRiseFall(const RiseFall *rf) const
{
  if (rf == RiseFall::rise())
    return "rise";
  else
    return "fall";
}

// Find the startpoint type from the first path edge.
std::string_view
ReportPath::edgeRegLatchDesc(const Edge *first_edge,
                             const TimingArc *first_arc) const
{
  const TimingRole *role = first_arc->role();
  if (role == TimingRole::latchDtoQ()) {
    Instance *inst = network_->instance(first_edge->to(graph_)->pin());
    LibertyCell *cell = network_->libertyCell(inst);
    if (cell) {
      const LibertyPort *enable_port;
      const FuncExpr *enable_func;
      const RiseFall *enable_rf;
      cell->latchEnable(first_edge->timingArcSet(),
                        enable_port, enable_func, enable_rf);
      return latchDesc(enable_rf);
    }
  }
  else if (role == TimingRole::regClkToQ())
    return regDesc(first_arc->fromEdge()->asRiseFall());
  else if (role == TimingRole::latchEnToQ())
    return latchDesc(first_arc->fromEdge()->asRiseFall());
  // Who knows...
  return regDesc(first_arc->fromEdge()->asRiseFall());
}

std::string_view
ReportPath::checkRegLatchDesc(const TimingRole *role,
                              const RiseFall *clk_rf) const
{
  if (role == TimingRole::regClkToQ())
    return regDesc(clk_rf);
  else if (role == TimingRole::latchEnToQ()
           || role == TimingRole::latchDtoQ())
    return latchDesc(clk_rf);
  else
    // Default when we don't know better.
    return "edge-triggered flip-flop";
}

std::string_view
ReportPath::regDesc(const RiseFall *clk_rf) const
{
  if (clk_rf == RiseFall::rise())
    return "rising edge-triggered flip-flop";
  else if (clk_rf == RiseFall::fall())
    return "falling edge-triggered flip-flop";
  else
    return "edge-triggered flip-flop";
}

std::string_view
ReportPath::latchDesc(const RiseFall *clk_rf) const
{
  return (clk_rf == RiseFall::rise()) 
    ? "positive level-sensitive latch"
    : "negative level-sensitive latch";
}

////////////////////////////////////////////////////////////////

static PinSeq
hierPinsThruEdge(const Edge *edge,
                 const Network *network,
                 const Graph *graph)
{
  const Pin *drvr_pin = edge->from(graph)->pin();
  const Pin *load_pin = edge->to(graph)->pin();
  PinSeq drvr_hpins;
  PinSeq load_hpins;
  hierPinsAbove(drvr_pin, network, drvr_hpins);
  hierPinsAbove(load_pin, network, load_hpins);
  if (drvr_hpins.empty()) {
    std::ranges::reverse(load_hpins);
    return load_hpins;
  }
  if (load_hpins.empty())
    return drvr_hpins;
  for (size_t l1 = 0; l1 < load_hpins.size(); l1++) {
    const Pin *load_hpin = load_hpins[l1];
    const Net *load_net = network->net(load_hpin);
    for (size_t d1 = 0; d1 < drvr_hpins.size(); d1++) {
      const Pin *drvr_hpin = drvr_hpins[d1];
      const Net *drvr_net = network->net(drvr_hpin);
      if (load_net == drvr_net) {
        PinSeq hpins_thru;
        for (size_t d2 = 0; d2 < d1; d2++) {
          const Pin *drvr_hpin2 = drvr_hpins[d2];
          hpins_thru.push_back(drvr_hpin2);
        }
        hpins_thru.push_back(drvr_hpin);
        hpins_thru.push_back(load_hpin);
        for (size_t l2 = 0; l2 < l1; l2++) {
          const Pin *load_hpin2 = load_hpins[l2];
          hpins_thru.push_back(load_hpin2);
        }
        return hpins_thru;
      }
    }
  }
  return PinSeq();
}

static void
hierPinsAbove(const Pin *pin,
              const Network *network,
              PinSeq &pins_above)
{
  const Net *net = network->net(pin);
  hierPinsAbove(net, network, pins_above);
}

static void
hierPinsAbove(const Net *net,
              const Network *network,
              PinSeq &pins_above)
{
  if (net) {
    NetTermIterator *term_iter = network->termIterator(net);
    while (term_iter->hasNext()) {
      const Term *term = term_iter->next();
      const Pin *net_pin = network->pin(term);
      if (network->isHierarchical(net_pin))
        pins_above.push_back(net_pin);
      const Net *hpin_net = network->net(net_pin);
      if (hpin_net)
        hierPinsAbove(hpin_net, network, pins_above);
    }
    delete term_iter;
  }
}

} // namespace sta
